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}