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:** 64 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:** None
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 pub(crate) statement_cache_capacity: usize,
85}
86
87impl OdbcConnectOptions {
88 pub fn connection_string(&self) -> &str {
89 &self.conn_str
90 }
91
92 /// Sets the buffer configuration for this connection.
93 ///
94 /// The buffer settings control memory usage and performance characteristics
95 /// when fetching data from ODBC data sources.
96 ///
97 /// # Example
98 /// ```rust,no_run
99 /// use std::str::FromStr;
100 /// use sqlx_core_oldapi::odbc::{OdbcConnectOptions, OdbcBufferSettings};
101 ///
102 /// let mut opts = OdbcConnectOptions::from_str("DSN=MyDataSource")?;
103 ///
104 /// // Configure for high-throughput buffered mode
105 /// opts.buffer_settings(OdbcBufferSettings {
106 /// batch_size: 256,
107 /// max_column_size: Some(2048),
108 /// });
109 ///
110 /// // Or configure for unbuffered mode
111 /// opts.buffer_settings(OdbcBufferSettings {
112 /// batch_size: 128, // batch_size is ignored in unbuffered mode
113 /// max_column_size: None,
114 /// });
115 /// # Ok::<(), sqlx_core_oldapi::error::Error>(())
116 /// ```
117 pub fn buffer_settings(&mut self, settings: OdbcBufferSettings) -> &mut Self {
118 self.buffer_settings = settings;
119 self
120 }
121
122 /// Sets the batch size for bulk fetch operations.
123 ///
124 /// This controls how many rows are fetched at once during query execution.
125 /// Higher values can improve performance for large result sets but use more memory.
126 /// Used in both buffered and unbuffered mode.
127 ///
128 /// # Panics
129 /// Panics if `batch_size` is 0.
130 pub fn batch_size(&mut self, batch_size: usize) -> &mut Self {
131 assert!(batch_size > 0, "batch_size must be greater than 0");
132 self.buffer_settings.batch_size = batch_size;
133 self
134 }
135
136 /// Sets the maximum column size for text and binary data.
137 ///
138 /// This controls the buffer size allocated for columns when the database
139 /// doesn't specify a maximum length. Larger values ensure complete data
140 /// capture but increase memory usage.
141 ///
142 /// - When set to `Some(value)`: Enables buffered mode with batch fetching
143 /// - When set to `None`: Enables unbuffered mode with row-by-row processing
144 ///
145 /// # Panics
146 /// Panics if `max_column_size` is `Some(0)`.
147 pub fn max_column_size(&mut self, max_column_size: Option<usize>) -> &mut Self {
148 if let Some(size) = max_column_size {
149 assert!(size > 0, "max_column_size must be greater than 0");
150 }
151 self.buffer_settings.max_column_size = max_column_size;
152 self
153 }
154
155 /// Returns the current buffer settings for this connection.
156 pub fn buffer_settings_ref(&self) -> &OdbcBufferSettings {
157 &self.buffer_settings
158 }
159
160 /// Sets the maximum number of prepared statements retained by each connection.
161 ///
162 /// Set this to `0` to disable statement caching.
163 ///
164 /// Default: 100.
165 pub fn statement_cache_capacity(&mut self, capacity: usize) -> &mut Self {
166 self.statement_cache_capacity = capacity;
167 self
168 }
169}
170
171impl Debug for OdbcConnectOptions {
172 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
173 f.debug_struct("OdbcConnectOptions")
174 .field("conn_str", &"<redacted>")
175 .field("buffer_settings", &self.buffer_settings)
176 .field("statement_cache_capacity", &self.statement_cache_capacity)
177 .finish()
178 }
179}
180
181impl FromStr for OdbcConnectOptions {
182 type Err = Error;
183
184 fn from_str(s: &str) -> Result<Self, Self::Err> {
185 // Accept forms:
186 // - "odbc:DSN=Name;..." -> strip scheme
187 // - "odbc:Name" -> interpret as DSN
188 // - "DSN=Name;..." or full ODBC connection string
189 let mut t = s.trim();
190 if let Some(rest) = t.strip_prefix("odbc:") {
191 t = rest;
192 }
193 let conn_str = if t.contains('=') {
194 // Looks like an ODBC key=value connection string
195 t.to_string()
196 } else {
197 // Bare DSN name
198 format!("DSN={}", t)
199 };
200
201 Ok(Self {
202 conn_str,
203 log_settings: LogSettings::default(),
204 buffer_settings: OdbcBufferSettings::default(),
205 statement_cache_capacity: 100,
206 })
207 }
208}
209
210impl ConnectOptions for OdbcConnectOptions {
211 type Connection = OdbcConnection;
212
213 fn connect(&self) -> BoxFuture<'_, Result<Self::Connection, Error>>
214 where
215 Self::Connection: Sized,
216 {
217 Box::pin(OdbcConnection::establish(self))
218 }
219
220 fn log_statements(&mut self, level: LevelFilter) -> &mut Self {
221 self.log_settings.log_statements(level);
222 self
223 }
224
225 fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self {
226 self.log_settings.log_slow_statements(level, duration);
227 self
228 }
229}