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: 64,
74            max_column_size: None,
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 0.
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!(size > 0, "max_column_size must be greater than 0");
149        }
150        self.buffer_settings.max_column_size = max_column_size;
151        self
152    }
153
154    /// Returns the current buffer settings for this connection.
155    pub fn buffer_settings_ref(&self) -> &OdbcBufferSettings {
156        &self.buffer_settings
157    }
158}
159
160impl Debug for OdbcConnectOptions {
161    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
162        f.debug_struct("OdbcConnectOptions")
163            .field("conn_str", &"<redacted>")
164            .field("buffer_settings", &self.buffer_settings)
165            .finish()
166    }
167}
168
169impl FromStr for OdbcConnectOptions {
170    type Err = Error;
171
172    fn from_str(s: &str) -> Result<Self, Self::Err> {
173        // Accept forms:
174        // - "odbc:DSN=Name;..." -> strip scheme
175        // - "odbc:Name" -> interpret as DSN
176        // - "DSN=Name;..." or full ODBC connection string
177        let mut t = s.trim();
178        if let Some(rest) = t.strip_prefix("odbc:") {
179            t = rest;
180        }
181        let conn_str = if t.contains('=') {
182            // Looks like an ODBC key=value connection string
183            t.to_string()
184        } else {
185            // Bare DSN name
186            format!("DSN={}", t)
187        };
188
189        Ok(Self {
190            conn_str,
191            log_settings: LogSettings::default(),
192            buffer_settings: OdbcBufferSettings::default(),
193        })
194    }
195}
196
197impl ConnectOptions for OdbcConnectOptions {
198    type Connection = OdbcConnection;
199
200    fn connect(&self) -> BoxFuture<'_, Result<Self::Connection, Error>>
201    where
202        Self::Connection: Sized,
203    {
204        Box::pin(OdbcConnection::establish(self))
205    }
206
207    fn log_statements(&mut self, level: LevelFilter) -> &mut Self {
208        self.log_settings.log_statements(level);
209        self
210    }
211
212    fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self {
213        self.log_settings.log_slow_statements(level, duration);
214        self
215    }
216}