sqlx_core_oldapi/odbc/options/
mod.rs

1use crate::connection::{ConnectOptions, LogSettings};
2use crate::error::Error;
3use futures_core::future::BoxFuture;
4use log::LevelFilter;
5use std::fmt::{self, Debug, Formatter};
6use std::str::FromStr;
7use std::time::Duration;
8
9use crate::odbc::OdbcConnection;
10
11/// Configuration for ODBC buffer settings that control memory usage and performance characteristics.
12///
13/// These settings affect how SQLx fetches and processes data from ODBC data sources. Careful tuning
14/// of these parameters can significantly impact memory usage and query performance.
15///
16/// # Buffered vs Unbuffered Mode
17///
18/// The buffer settings control two different data fetching strategies based on `max_column_size`:
19///
20/// **Buffered Mode** (when `max_column_size` is `Some(value)`):
21/// - Fetches rows in batches using columnar buffers for better performance with large result sets
22/// - Pre-allocates memory for all columns and rows in each batch using `ColumnarAnyBuffer`
23/// - WARNING: Long textual and binary field data will be truncated to `max_column_size`
24/// - Better for high-throughput scenarios where memory usage is acceptable
25/// - Uses `batch_size` to control how many rows are fetched at once
26///
27/// **Unbuffered Mode** (when `max_column_size` is `None`):
28/// - Fetches rows in batches using the `next_row()` method for memory efficiency
29/// - Processes rows in batches of `batch_size` but without pre-allocating columnar buffers
30/// - No data truncation - handles variable-sized data dynamically
31/// - More memory efficient than buffered mode but potentially slower
32/// - Better for scenarios where data sizes vary significantly or memory is constrained
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct OdbcBufferSettings {
35    /// The number of rows to fetch in each batch during bulk operations.
36    ///
37    /// **Performance Impact:**
38    /// - Higher values reduce the number of round-trips to the database but increase memory usage
39    /// - Lower values reduce memory usage but may increase latency due to more frequent fetches
40    /// - Typical range: 32-512 rows
41    /// - Used in both buffered and unbuffered modes
42    ///
43    /// **Memory Impact:**
44    /// - In buffered mode: Each batch allocates buffers for `batch_size * number_of_columns` cells
45    /// - In unbuffered mode: Each batch processes `batch_size` rows without pre-allocation
46    /// - For wide result sets, this can consume significant memory
47    ///
48    /// **Default:** 128 rows
49    pub batch_size: usize,
50
51    /// The maximum size (in characters) for text and binary columns when the database doesn't specify a length.
52    ///
53    /// **Performance Impact:**
54    /// - When `Some(value)`: Enables buffered mode with batch fetching and pre-allocated buffers
55    /// - When `None`: Enables unbuffered mode with batched row-by-row processing
56    /// - Higher values ensure large text fields are fully captured but increase memory allocation
57    /// - Lower values may truncate data but reduce memory pressure
58    /// - Affects VARCHAR, NVARCHAR, TEXT, and BLOB column types
59    ///
60    /// **Memory Impact:**
61    /// - When `Some(value)`: Directly controls buffer size for variable-length columns
62    /// - When `None`: Minimal memory allocation per row, no truncation
63    /// - Setting too high can waste memory; setting too low can cause data truncation
64    /// - Consider your data characteristics when tuning this value
65    ///
66    /// **Default:** Some(4096)
67    pub max_column_size: Option<usize>,
68}
69
70impl Default for OdbcBufferSettings {
71    fn default() -> Self {
72        Self {
73            batch_size: 128,
74            max_column_size: Some(4096),
75        }
76    }
77}
78
79#[derive(Clone)]
80pub struct OdbcConnectOptions {
81    pub(crate) conn_str: String,
82    pub(crate) log_settings: LogSettings,
83    pub(crate) buffer_settings: OdbcBufferSettings,
84}
85
86impl OdbcConnectOptions {
87    pub fn connection_string(&self) -> &str {
88        &self.conn_str
89    }
90
91    /// Sets the buffer configuration for this connection.
92    ///
93    /// The buffer settings control memory usage and performance characteristics
94    /// when fetching data from ODBC data sources.
95    ///
96    /// # Example
97    /// ```rust,no_run
98    /// use std::str::FromStr;
99    /// use sqlx_core_oldapi::odbc::{OdbcConnectOptions, OdbcBufferSettings};
100    ///
101    /// let mut opts = OdbcConnectOptions::from_str("DSN=MyDataSource")?;
102    ///
103    /// // Configure for high-throughput buffered mode
104    /// opts.buffer_settings(OdbcBufferSettings {
105    ///     batch_size: 256,
106    ///     max_column_size: Some(2048),
107    /// });
108    ///
109    /// // Or configure for unbuffered mode
110    /// opts.buffer_settings(OdbcBufferSettings {
111    ///     batch_size: 128,  // batch_size is ignored in unbuffered mode
112    ///     max_column_size: None,
113    /// });
114    /// # Ok::<(), sqlx_core_oldapi::error::Error>(())
115    /// ```
116    pub fn buffer_settings(&mut self, settings: OdbcBufferSettings) -> &mut Self {
117        self.buffer_settings = settings;
118        self
119    }
120
121    /// Sets the batch size for bulk fetch operations.
122    ///
123    /// This controls how many rows are fetched at once during query execution.
124    /// Higher values can improve performance for large result sets but use more memory.
125    /// Only used when `max_column_size` is `Some(value)` (buffered mode).
126    ///
127    /// # Panics
128    /// Panics if `batch_size` is 0.
129    pub fn batch_size(&mut self, batch_size: usize) -> &mut Self {
130        assert!(batch_size > 0, "batch_size must be greater than 0");
131        self.buffer_settings.batch_size = batch_size;
132        self
133    }
134
135    /// Sets the maximum column size for text and binary data.
136    ///
137    /// This controls the buffer size allocated for columns when the database
138    /// doesn't specify a maximum length. Larger values ensure complete data
139    /// capture but increase memory usage.
140    ///
141    /// - When set to `Some(value)`: Enables buffered mode with batch fetching
142    /// - When set to `None`: Enables unbuffered mode with row-by-row processing
143    ///
144    /// # Panics
145    /// Panics if `max_column_size` is less than 1024 or greater than 4096 (when Some).
146    pub fn max_column_size(&mut self, max_column_size: Option<usize>) -> &mut Self {
147        if let Some(size) = max_column_size {
148            assert!(
149                (1024..=4096).contains(&size),
150                "max_column_size must be between 1024 and 4096"
151            );
152        }
153        self.buffer_settings.max_column_size = max_column_size;
154        self
155    }
156
157    /// Returns the current buffer settings for this connection.
158    pub fn buffer_settings_ref(&self) -> &OdbcBufferSettings {
159        &self.buffer_settings
160    }
161}
162
163impl Debug for OdbcConnectOptions {
164    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
165        f.debug_struct("OdbcConnectOptions")
166            .field("conn_str", &"<redacted>")
167            .field("buffer_settings", &self.buffer_settings)
168            .finish()
169    }
170}
171
172impl FromStr for OdbcConnectOptions {
173    type Err = Error;
174
175    fn from_str(s: &str) -> Result<Self, Self::Err> {
176        // Accept forms:
177        // - "odbc:DSN=Name;..." -> strip scheme
178        // - "odbc:Name" -> interpret as DSN
179        // - "DSN=Name;..." or full ODBC connection string
180        let mut t = s.trim();
181        if let Some(rest) = t.strip_prefix("odbc:") {
182            t = rest;
183        }
184        let conn_str = if t.contains('=') {
185            // Looks like an ODBC key=value connection string
186            t.to_string()
187        } else {
188            // Bare DSN name
189            format!("DSN={}", t)
190        };
191
192        Ok(Self {
193            conn_str,
194            log_settings: LogSettings::default(),
195            buffer_settings: OdbcBufferSettings::default(),
196        })
197    }
198}
199
200impl ConnectOptions for OdbcConnectOptions {
201    type Connection = OdbcConnection;
202
203    fn connect(&self) -> BoxFuture<'_, Result<Self::Connection, Error>>
204    where
205        Self::Connection: Sized,
206    {
207        Box::pin(OdbcConnection::establish(self))
208    }
209
210    fn log_statements(&mut self, level: LevelFilter) -> &mut Self {
211        self.log_settings.log_statements(level);
212        self
213    }
214
215    fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self {
216        self.log_settings.log_slow_statements(level, duration);
217        self
218    }
219}