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}