Skip to main content

datafusion_execution/
config.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::{collections::HashMap, sync::Arc};
19
20use datafusion_common::{
21    Result, ScalarValue,
22    config::{ConfigExtension, ConfigNonZeroUsize, ConfigOptions, SpillCompression},
23    extensions::Extensions,
24};
25
26/// Configuration options for [`SessionContext`].
27///
28/// Can be passed to [`SessionContext::new_with_config`] to customize the configuration of DataFusion.
29///
30/// Options can be set using namespaces keys with `.` as the separator, where the
31/// namespace determines which configuration struct the value to routed to. All
32/// built-in options are under the `datafusion` namespace.
33///
34/// For example, the key `datafusion.execution.batch_size` will set [ExecutionOptions::batch_size][datafusion_common::config::ExecutionOptions::batch_size],
35/// because [ConfigOptions::execution] is [ExecutionOptions][datafusion_common::config::ExecutionOptions]. Similarly, the key
36/// `datafusion.execution.parquet.pushdown_filters` will set [ParquetOptions::pushdown_filters][datafusion_common::config::ParquetOptions::pushdown_filters],
37/// since [ExecutionOptions::parquet][datafusion_common::config::ExecutionOptions::parquet] is [ParquetOptions][datafusion_common::config::ParquetOptions].
38///
39/// Some options have convenience methods. For example [SessionConfig::with_batch_size] is
40/// shorthand for setting `datafusion.execution.batch_size`.
41///
42/// ```
43/// use datafusion_common::ScalarValue;
44/// use datafusion_execution::config::SessionConfig;
45///
46/// let config = SessionConfig::new()
47///     .set(
48///         "datafusion.execution.batch_size",
49///         &ScalarValue::UInt64(Some(1234)),
50///     )
51///     .set_bool("datafusion.execution.parquet.pushdown_filters", true);
52///
53/// assert_eq!(config.batch_size(), 1234);
54/// assert_eq!(config.options().execution.batch_size.get(), 1234);
55/// assert_eq!(config.options().execution.parquet.pushdown_filters, true);
56/// ```
57///
58/// You can also directly mutate the options via [SessionConfig::options_mut].
59/// So the following is equivalent to the above:
60///
61/// ```
62/// # use datafusion_execution::config::SessionConfig;
63/// # use datafusion_common::config::ConfigNonZeroUsize;
64/// #
65/// let mut config = SessionConfig::new();
66/// config.options_mut().execution.batch_size = ConfigNonZeroUsize::try_new(1234)?;
67/// config.options_mut().execution.parquet.pushdown_filters = true;
68/// #
69/// # assert_eq!(config.batch_size(), 1234);
70/// # assert_eq!(config.options().execution.batch_size.get(), 1234);
71/// # assert_eq!(config.options().execution.parquet.pushdown_filters, true);
72/// # datafusion_common::Result::<()>::Ok(())
73/// ```
74///
75/// ## Built-in options
76///
77/// | Namespace | Config struct |
78/// | --------- | ------------- |
79/// | `datafusion.catalog` | [CatalogOptions][datafusion_common::config::CatalogOptions] |
80/// | `datafusion.execution` | [ExecutionOptions][datafusion_common::config::ExecutionOptions] |
81/// | `datafusion.execution.parquet` | [ParquetOptions][datafusion_common::config::ParquetOptions] |
82/// | `datafusion.optimizer` | [OptimizerOptions][datafusion_common::config::OptimizerOptions] |
83/// | `datafusion.sql_parser` | [SqlParserOptions][datafusion_common::config::SqlParserOptions] |
84/// | `datafusion.explain` | [ExplainOptions][datafusion_common::config::ExplainOptions] |
85///
86/// ## Custom configuration
87///
88/// Configuration options can be extended. See [SessionConfig::with_extension] for details.
89///
90/// [`SessionContext`]: https://docs.rs/datafusion/latest/datafusion/execution/context/struct.SessionContext.html
91/// [`SessionContext::new_with_config`]: https://docs.rs/datafusion/latest/datafusion/execution/context/struct.SessionContext.html#method.new_with_config
92#[derive(Clone, Debug)]
93pub struct SessionConfig {
94    /// Configuration options for the current session.
95    ///
96    /// A new copy is created on write, if there are other outstanding
97    /// references to the same options.
98    options: Arc<ConfigOptions>,
99    /// Opaque extensions, keyed by concrete Rust type. See
100    /// [`with_extension`](Self::with_extension) and
101    /// [`get_extension`](Self::get_extension).
102    extensions: Extensions,
103}
104
105impl Default for SessionConfig {
106    fn default() -> Self {
107        Self {
108            options: Arc::new(ConfigOptions::new()),
109            extensions: Extensions::new(),
110        }
111    }
112}
113
114impl SessionConfig {
115    /// Create an execution config with default setting
116    pub fn new() -> Self {
117        Default::default()
118    }
119
120    /// Create an execution config with config options read from the environment
121    ///
122    /// See [`ConfigOptions::from_env`] for details on how environment variables
123    /// are mapped to config options.
124    pub fn from_env() -> Result<Self> {
125        Ok(ConfigOptions::from_env()?.into())
126    }
127
128    /// Create new ConfigOptions struct, taking values from a string hash map.
129    pub fn from_string_hash_map(settings: &HashMap<String, String>) -> Result<Self> {
130        Ok(ConfigOptions::from_string_hash_map(settings)?.into())
131    }
132
133    /// Return a handle to the configuration options.
134    ///
135    /// Can be used to read the current configuration.
136    ///
137    /// ```
138    /// use datafusion_execution::config::SessionConfig;
139    ///
140    /// let config = SessionConfig::new();
141    /// assert!(config.options().execution.batch_size.get() > 0);
142    /// ```
143    pub fn options(&self) -> &Arc<ConfigOptions> {
144        &self.options
145    }
146
147    /// Return a mutable handle to the configuration options.
148    ///
149    /// Can be used to set configuration options.
150    ///
151    /// ```
152    /// use datafusion_common::config::ConfigNonZeroUsize;
153    /// use datafusion_execution::config::SessionConfig;
154    ///
155    /// let mut config = SessionConfig::new();
156    /// config.options_mut().execution.batch_size = ConfigNonZeroUsize::try_new(1024)?;
157    /// assert_eq!(config.options().execution.batch_size.get(), 1024);
158    /// # datafusion_common::Result::<()>::Ok(())
159    /// ```
160    pub fn options_mut(&mut self) -> &mut ConfigOptions {
161        Arc::make_mut(&mut self.options)
162    }
163
164    /// Set a configuration option
165    pub fn set(self, key: &str, value: &ScalarValue) -> Self {
166        self.set_str(key, &value.to_string())
167    }
168
169    /// Set a boolean configuration option
170    pub fn set_bool(self, key: &str, value: bool) -> Self {
171        self.set_str(key, &value.to_string())
172    }
173
174    /// Set a generic `u64` configuration option
175    pub fn set_u64(self, key: &str, value: u64) -> Self {
176        self.set_str(key, &value.to_string())
177    }
178
179    /// Set a generic `usize` configuration option
180    pub fn set_usize(self, key: &str, value: usize) -> Self {
181        self.set_str(key, &value.to_string())
182    }
183
184    /// Set a generic `str` configuration option
185    pub fn set_str(mut self, key: &str, value: &str) -> Self {
186        self.options_mut().set(key, value).unwrap();
187        self
188    }
189
190    /// Customize batch size
191    pub fn with_batch_size(mut self, n: usize) -> Self {
192        self.options_mut().execution.batch_size =
193            ConfigNonZeroUsize::try_new(n).expect("batch size must be greater than zero");
194        self
195    }
196
197    /// Customize [`target_partitions`]
198    ///
199    /// [`target_partitions`]: datafusion_common::config::ExecutionOptions::target_partitions
200    pub fn with_target_partitions(mut self, n: usize) -> Self {
201        self.options_mut().execution.target_partitions = if n == 0 {
202            datafusion_common::config::ExecutionOptions::default().target_partitions
203        } else {
204            n
205        };
206        self
207    }
208
209    /// Insert new [ConfigExtension]
210    pub fn with_option_extension<T: ConfigExtension>(mut self, extension: T) -> Self {
211        self.options_mut().extensions.insert(extension);
212        self
213    }
214
215    /// Get [`target_partitions`]
216    ///
217    /// [`target_partitions`]: datafusion_common::config::ExecutionOptions::target_partitions
218    pub fn target_partitions(&self) -> usize {
219        self.options.execution.target_partitions
220    }
221
222    /// Is the information schema enabled?
223    pub fn information_schema(&self) -> bool {
224        self.options.catalog.information_schema
225    }
226
227    /// Should the context create the default catalog and schema?
228    pub fn create_default_catalog_and_schema(&self) -> bool {
229        self.options.catalog.create_default_catalog_and_schema
230    }
231
232    /// Are joins repartitioned during execution?
233    pub fn repartition_joins(&self) -> bool {
234        self.options.optimizer.repartition_joins
235    }
236
237    /// Are aggregates repartitioned during execution?
238    pub fn repartition_aggregations(&self) -> bool {
239        self.options.optimizer.repartition_aggregations
240    }
241
242    /// Are window functions repartitioned during execution?
243    pub fn repartition_window_functions(&self) -> bool {
244        self.options.optimizer.repartition_windows
245    }
246
247    /// Do we execute sorts in a per-partition fashion and merge afterwards,
248    /// or do we coalesce partitions first and sort globally?
249    pub fn repartition_sorts(&self) -> bool {
250        self.options.optimizer.repartition_sorts
251    }
252
253    /// Prefer existing sort (true) or maximize parallelism (false). See
254    /// [prefer_existing_sort] for more details
255    ///
256    /// [prefer_existing_sort]: datafusion_common::config::OptimizerOptions::prefer_existing_sort
257    pub fn prefer_existing_sort(&self) -> bool {
258        self.options.optimizer.prefer_existing_sort
259    }
260
261    /// Are statistics collected during execution?
262    pub fn collect_statistics(&self) -> bool {
263        self.options.execution.collect_statistics
264    }
265
266    /// Compression codec for spill file
267    pub fn spill_compression(&self) -> SpillCompression {
268        self.options.execution.spill_compression
269    }
270
271    /// Selects a name for the default catalog and schema
272    pub fn with_default_catalog_and_schema(
273        mut self,
274        catalog: impl Into<String>,
275        schema: impl Into<String>,
276    ) -> Self {
277        self.options_mut().catalog.default_catalog = catalog.into();
278        self.options_mut().catalog.default_schema = schema.into();
279        self
280    }
281
282    /// Controls whether the default catalog and schema will be automatically created
283    pub fn with_create_default_catalog_and_schema(mut self, create: bool) -> Self {
284        self.options_mut().catalog.create_default_catalog_and_schema = create;
285        self
286    }
287
288    /// Enables or disables the inclusion of `information_schema` virtual tables
289    pub fn with_information_schema(mut self, enabled: bool) -> Self {
290        self.options_mut().catalog.information_schema = enabled;
291        self
292    }
293
294    /// Enables or disables the use of repartitioning for joins to improve parallelism
295    pub fn with_repartition_joins(mut self, enabled: bool) -> Self {
296        self.options_mut().optimizer.repartition_joins = enabled;
297        self
298    }
299
300    /// Enables or disables the use of repartitioning for aggregations to improve parallelism
301    pub fn with_repartition_aggregations(mut self, enabled: bool) -> Self {
302        self.options_mut().optimizer.repartition_aggregations = enabled;
303        self
304    }
305
306    /// Sets minimum file range size for repartitioning scans
307    pub fn with_repartition_file_min_size(mut self, size: usize) -> Self {
308        self.options_mut().optimizer.repartition_file_min_size = size;
309        self
310    }
311
312    /// Enables or disables the allowing unordered symmetric hash join
313    pub fn with_allow_symmetric_joins_without_pruning(mut self, enabled: bool) -> Self {
314        self.options_mut()
315            .optimizer
316            .allow_symmetric_joins_without_pruning = enabled;
317        self
318    }
319
320    /// Enables or disables the use of repartitioning for file scans
321    pub fn with_repartition_file_scans(mut self, enabled: bool) -> Self {
322        self.options_mut().optimizer.repartition_file_scans = enabled;
323        self
324    }
325
326    /// Enables or disables the use of repartitioning for window functions to improve parallelism
327    pub fn with_repartition_windows(mut self, enabled: bool) -> Self {
328        self.options_mut().optimizer.repartition_windows = enabled;
329        self
330    }
331
332    /// Enables or disables the use of per-partition sorting to improve parallelism
333    pub fn with_repartition_sorts(mut self, enabled: bool) -> Self {
334        self.options_mut().optimizer.repartition_sorts = enabled;
335        self
336    }
337
338    /// Prefer existing sort (true) or maximize parallelism (false). See
339    /// [prefer_existing_sort] for more details
340    ///
341    /// [prefer_existing_sort]: datafusion_common::config::OptimizerOptions::prefer_existing_sort
342    pub fn with_prefer_existing_sort(mut self, enabled: bool) -> Self {
343        self.options_mut().optimizer.prefer_existing_sort = enabled;
344        self
345    }
346
347    /// Prefer existing union (true). See [prefer_existing_union] for more details
348    ///
349    /// [prefer_existing_union]: datafusion_common::config::OptimizerOptions::prefer_existing_union
350    pub fn with_prefer_existing_union(mut self, enabled: bool) -> Self {
351        self.options_mut().optimizer.prefer_existing_union = enabled;
352        self
353    }
354
355    /// Enables or disables the use of pruning predicate for parquet readers to skip row groups
356    pub fn with_parquet_pruning(mut self, enabled: bool) -> Self {
357        self.options_mut().execution.parquet.pruning = enabled;
358        self
359    }
360
361    /// Returns true if pruning predicate should be used to skip parquet row groups
362    pub fn parquet_pruning(&self) -> bool {
363        self.options.execution.parquet.pruning
364    }
365
366    /// Returns true if bloom filter should be used to skip parquet row groups
367    pub fn parquet_bloom_filter_pruning(&self) -> bool {
368        self.options.execution.parquet.bloom_filter_on_read
369    }
370
371    /// Enables or disables the use of bloom filter for parquet readers to skip row groups
372    pub fn with_parquet_bloom_filter_pruning(mut self, enabled: bool) -> Self {
373        self.options_mut().execution.parquet.bloom_filter_on_read = enabled;
374        self
375    }
376
377    /// Returns true if page index should be used to skip parquet data pages
378    pub fn parquet_page_index_pruning(&self) -> bool {
379        self.options.execution.parquet.enable_page_index
380    }
381
382    /// Enables or disables the use of page index for parquet readers to skip parquet data pages
383    pub fn with_parquet_page_index_pruning(mut self, enabled: bool) -> Self {
384        self.options_mut().execution.parquet.enable_page_index = enabled;
385        self
386    }
387
388    /// Enables or disables the collection of statistics after listing files
389    pub fn with_collect_statistics(mut self, enabled: bool) -> Self {
390        self.options_mut().execution.collect_statistics = enabled;
391        self
392    }
393
394    /// Get the currently configured batch size
395    pub fn batch_size(&self) -> usize {
396        self.options.execution.batch_size.get()
397    }
398
399    /// Enables or disables the coalescence of small batches into larger batches
400    pub fn with_coalesce_batches(mut self, enabled: bool) -> Self {
401        self.options_mut().execution.coalesce_batches = enabled;
402        self
403    }
404
405    /// Returns true if record batches will be examined between each operator
406    /// and small batches will be coalesced into larger batches.
407    pub fn coalesce_batches(&self) -> bool {
408        self.options.execution.coalesce_batches
409    }
410
411    /// Enables or disables the round robin repartition for increasing parallelism
412    pub fn with_round_robin_repartition(mut self, enabled: bool) -> Self {
413        self.options_mut().optimizer.enable_round_robin_repartition = enabled;
414        self
415    }
416
417    /// Returns true if the physical plan optimizer will try to
418    /// add round robin repartition to increase parallelism to leverage more CPU cores.
419    pub fn round_robin_repartition(&self) -> bool {
420        self.options.optimizer.enable_round_robin_repartition
421    }
422
423    /// Enables or disables sort pushdown optimization, and currently only
424    /// applies to Parquet data source.
425    pub fn with_enable_sort_pushdown(mut self, enabled: bool) -> Self {
426        self.options_mut().optimizer.enable_sort_pushdown = enabled;
427        self
428    }
429
430    /// Enables or disables elimination of `ORDER BY` clauses in subqueries
431    /// when they are not required by order-sensitive operators.
432    pub fn with_enable_subquery_sort_elimination(mut self, enabled: bool) -> Self {
433        self.options_mut()
434            .sql_parser
435            .enable_subquery_sort_elimination = enabled;
436        self
437    }
438
439    /// Set the size of [`sort_spill_reservation_bytes`] to control
440    /// memory pre-reservation
441    ///
442    /// [`sort_spill_reservation_bytes`]: datafusion_common::config::ExecutionOptions::sort_spill_reservation_bytes
443    pub fn with_sort_spill_reservation_bytes(
444        mut self,
445        sort_spill_reservation_bytes: usize,
446    ) -> Self {
447        self.options_mut().execution.sort_spill_reservation_bytes =
448            sort_spill_reservation_bytes;
449        self
450    }
451
452    /// Set the compression codec [`spill_compression`] used when spilling data to disk.
453    ///
454    /// [`spill_compression`]: datafusion_common::config::ExecutionOptions::spill_compression
455    pub fn with_spill_compression(mut self, spill_compression: SpillCompression) -> Self {
456        self.options_mut().execution.spill_compression = spill_compression;
457        self
458    }
459
460    /// Set the size of [`sort_in_place_threshold_bytes`] to control
461    /// how sort does things.
462    ///
463    /// [`sort_in_place_threshold_bytes`]: datafusion_common::config::ExecutionOptions::sort_in_place_threshold_bytes
464    pub fn with_sort_in_place_threshold_bytes(
465        mut self,
466        sort_in_place_threshold_bytes: usize,
467    ) -> Self {
468        self.options_mut().execution.sort_in_place_threshold_bytes =
469            sort_in_place_threshold_bytes;
470        self
471    }
472
473    /// Enables or disables the enforcement of batch size in joins
474    pub fn with_enforce_batch_size_in_joins(
475        mut self,
476        enforce_batch_size_in_joins: bool,
477    ) -> Self {
478        self.options_mut().execution.enforce_batch_size_in_joins =
479            enforce_batch_size_in_joins;
480        self
481    }
482
483    /// Returns true if the joins will be enforced to output batches of the configured size
484    pub fn enforce_batch_size_in_joins(&self) -> bool {
485        self.options.execution.enforce_batch_size_in_joins
486    }
487
488    /// Toggle SQL ANSI mode for expressions, casting, and error handling
489    pub fn with_enable_ansi_mode(mut self, enable_ansi_mode: bool) -> Self {
490        self.options_mut().execution.enable_ansi_mode = enable_ansi_mode;
491        self
492    }
493
494    /// Convert configuration options to name-value pairs with values
495    /// converted to strings.
496    ///
497    /// Note that this method will eventually be deprecated and
498    /// replaced by [`options`].
499    ///
500    /// [`options`]: Self::options
501    pub fn to_props(&self) -> HashMap<String, String> {
502        let mut map = HashMap::new();
503        // copy configs from config_options
504        for entry in self.options.entries() {
505            map.insert(entry.key, entry.value.unwrap_or_default());
506        }
507
508        map
509    }
510
511    /// Add extensions.
512    ///
513    /// Extensions can be used to attach extra data to the session config -- e.g. tracing information or caches.
514    /// Extensions are opaque and the types are unknown to DataFusion itself, which makes them extremely flexible. [^1]
515    ///
516    /// Extensions are stored within an [`Arc`] so they do NOT require [`Clone`]. The are immutable. If you need to
517    /// modify their state over their lifetime -- e.g. for caches -- you need to establish some form of interior mutability.
518    ///
519    /// Extensions are indexed by their type `T`. If multiple values of the same type are provided, only the last one
520    /// will be kept.
521    ///
522    /// You may use [`get_extension`](Self::get_extension) to retrieve extensions.
523    ///
524    /// # Example
525    /// ```
526    /// use datafusion_execution::config::SessionConfig;
527    /// use std::sync::Arc;
528    ///
529    /// // application-specific extension types
530    /// struct Ext1(u8);
531    /// struct Ext2(u8);
532    /// struct Ext3(u8);
533    ///
534    /// let ext1a = Arc::new(Ext1(10));
535    /// let ext1b = Arc::new(Ext1(11));
536    /// let ext2 = Arc::new(Ext2(2));
537    ///
538    /// let cfg = SessionConfig::default()
539    ///     // will only remember the last Ext1
540    ///     .with_extension(Arc::clone(&ext1a))
541    ///     .with_extension(Arc::clone(&ext1b))
542    ///     .with_extension(Arc::clone(&ext2));
543    ///
544    /// let ext1_received = cfg.get_extension::<Ext1>().unwrap();
545    /// assert!(!Arc::ptr_eq(&ext1_received, &ext1a));
546    /// assert!(Arc::ptr_eq(&ext1_received, &ext1b));
547    ///
548    /// let ext2_received = cfg.get_extension::<Ext2>().unwrap();
549    /// assert!(Arc::ptr_eq(&ext2_received, &ext2));
550    ///
551    /// assert!(cfg.get_extension::<Ext3>().is_none());
552    /// ```
553    ///
554    /// [^1]: Compare that to [`ConfigOptions`] which only supports [`ScalarValue`] payloads.
555    pub fn with_extension<T>(mut self, ext: Arc<T>) -> Self
556    where
557        T: Send + Sync + 'static,
558    {
559        self.set_extension(ext);
560        self
561    }
562
563    /// Set extension. Pretty much the same as [`with_extension`](Self::with_extension), but take
564    /// mutable reference instead of owning it. Useful if you want to add another extension after
565    /// the [`SessionConfig`] is created.
566    ///
567    /// # Example
568    /// ```
569    /// use datafusion_execution::config::SessionConfig;
570    /// use std::sync::Arc;
571    ///
572    /// // application-specific extension types
573    /// struct Ext1(u8);
574    /// struct Ext2(u8);
575    /// struct Ext3(u8);
576    ///
577    /// let ext1a = Arc::new(Ext1(10));
578    /// let ext1b = Arc::new(Ext1(11));
579    /// let ext2 = Arc::new(Ext2(2));
580    ///
581    /// let mut cfg = SessionConfig::default();
582    ///
583    /// // will only remember the last Ext1
584    /// cfg.set_extension(Arc::clone(&ext1a));
585    /// cfg.set_extension(Arc::clone(&ext1b));
586    /// cfg.set_extension(Arc::clone(&ext2));
587    ///
588    /// let ext1_received = cfg.get_extension::<Ext1>().unwrap();
589    /// assert!(!Arc::ptr_eq(&ext1_received, &ext1a));
590    /// assert!(Arc::ptr_eq(&ext1_received, &ext1b));
591    ///
592    /// let ext2_received = cfg.get_extension::<Ext2>().unwrap();
593    /// assert!(Arc::ptr_eq(&ext2_received, &ext2));
594    ///
595    /// assert!(cfg.get_extension::<Ext3>().is_none());
596    /// ```
597    pub fn set_extension<T>(&mut self, ext: Arc<T>)
598    where
599        T: Send + Sync + 'static,
600    {
601        self.extensions.insert_arc(ext);
602    }
603
604    /// Get extension, if any for the specified type `T` exists.
605    ///
606    /// See [`with_extension`](Self::with_extension) on how to add attach extensions.
607    pub fn get_extension<T>(&self) -> Option<Arc<T>>
608    where
609        T: Send + Sync + 'static,
610    {
611        self.extensions.get_arc::<T>()
612    }
613}
614
615impl From<ConfigOptions> for SessionConfig {
616    fn from(options: ConfigOptions) -> Self {
617        let options = Arc::new(options);
618        Self {
619            options,
620            ..Default::default()
621        }
622    }
623}