sqlx_exasol/responses/
attributes.rs

1use std::{borrow::Cow, num::NonZeroUsize, ops::Not};
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5/// Struct representing attributes related to the connection with the Exasol server.
6/// These can either be returned by an explicit `getAttributes` call or as part of any response.
7///
8/// Note that some of these are *read-only*!
9/// See the [specification](<https://github.com/exasol/websocket-api/blob/master/docs/WebsocketAPIV1.md#attributes-session-and-database-properties>)
10/// for more details.
11///
12/// Moreover, we store in this other custom connection related attributes, specific to the driver.
13#[derive(Clone, Debug)]
14pub struct ExaAttributes {
15    read_write: ExaRwAttributes<'static>,
16    read_only: ExaRoAttributes,
17    driver: ExaDriverAttributes,
18}
19
20impl ExaAttributes {
21    pub(crate) fn new(
22        compression_enabled: bool,
23        fetch_size: usize,
24        encryption_enabled: bool,
25        statement_cache_capacity: NonZeroUsize,
26    ) -> Self {
27        Self {
28            read_write: ExaRwAttributes::default(),
29            read_only: ExaRoAttributes::new(compression_enabled),
30            driver: ExaDriverAttributes::new(
31                fetch_size,
32                encryption_enabled,
33                statement_cache_capacity,
34            ),
35        }
36    }
37
38    #[must_use]
39    pub fn autocommit(&self) -> bool {
40        self.read_write.autocommit
41    }
42
43    #[deprecated = "use Connection::begin() to start a transaction"]
44    pub fn set_autocommit(&mut self, autocommit: bool) -> &mut Self {
45        self.driver.needs_send = true;
46        self.read_write.autocommit = autocommit;
47        self.driver.open_transaction = !autocommit;
48        self
49    }
50
51    #[must_use]
52    pub fn current_schema(&self) -> Option<&str> {
53        self.read_write.current_schema.as_deref()
54    }
55
56    /// Note that setting the open schema to `None` cannot be done
57    /// through this attribute. It can only be changed to a different one.
58    ///
59    /// An explicit `CLOSE SCHEMA;` statement would have to be executed
60    /// to accomplish the `no open schema` behavior.
61    pub fn set_current_schema(&mut self, schema: String) -> &mut Self {
62        self.driver.needs_send = true;
63        self.read_write.current_schema = Some(schema.into());
64        self
65    }
66
67    #[must_use]
68    pub fn feedback_interval(&self) -> u64 {
69        self.read_write.feedback_interval
70    }
71
72    pub fn set_feedback_interval(&mut self, feedback_interval: u64) -> &mut Self {
73        self.driver.needs_send = true;
74        self.read_write.feedback_interval = feedback_interval;
75        self
76    }
77
78    #[must_use]
79    pub fn numeric_characters(&self) -> &str {
80        &self.read_write.numeric_characters
81    }
82
83    pub fn set_numeric_characters(&mut self, numeric_characters: String) -> &mut Self {
84        self.driver.needs_send = true;
85        self.read_write.numeric_characters = numeric_characters.into();
86        self
87    }
88
89    #[must_use]
90    pub fn query_timeout(&self) -> u64 {
91        self.read_write.query_timeout
92    }
93
94    #[must_use]
95    pub fn set_query_timeout(&mut self, query_timeout: u64) -> &mut Self {
96        self.driver.needs_send = true;
97        self.read_write.query_timeout = query_timeout;
98        self
99    }
100
101    #[must_use]
102    pub fn snapshot_transactions_enabled(&self) -> bool {
103        self.read_write.snapshot_transactions_enabled
104    }
105
106    pub fn set_snapshot_transactions_enabled(&mut self, enabled: bool) -> &mut Self {
107        self.driver.needs_send = true;
108        self.read_write.snapshot_transactions_enabled = enabled;
109        self
110    }
111
112    #[must_use]
113    pub fn timestamp_utc_enabled(&self) -> bool {
114        self.read_write.timestamp_utc_enabled
115    }
116
117    pub fn set_timestamp_utc_enabled(&mut self, enabled: bool) -> &mut Self {
118        self.driver.needs_send = true;
119        self.read_write.timestamp_utc_enabled = enabled;
120        self
121    }
122
123    #[must_use]
124    pub fn compression_enabled(&self) -> bool {
125        self.read_only.compression_enabled
126    }
127
128    #[must_use]
129    pub fn date_format(&self) -> &str {
130        &self.read_only.date_format
131    }
132
133    #[must_use]
134    pub fn date_language(&self) -> &str {
135        &self.read_only.date_language
136    }
137
138    #[must_use]
139    pub fn datetime_format(&self) -> &str {
140        &self.read_only.datetime_format
141    }
142
143    #[must_use]
144    pub fn default_like_escape_character(&self) -> &str {
145        &self.read_only.default_like_escape_character
146    }
147
148    #[must_use]
149    pub fn timezone(&self) -> &str {
150        &self.read_only.timezone
151    }
152
153    #[must_use]
154    pub fn timezone_behavior(&self) -> &str {
155        &self.read_only.timezone_behavior
156    }
157
158    #[must_use]
159    pub fn open_transaction(&self) -> bool {
160        self.driver.open_transaction
161    }
162
163    #[must_use]
164    pub fn fetch_size(&self) -> usize {
165        self.driver.fetch_size
166    }
167
168    pub fn set_fetch_size(&mut self, fetch_size: usize) -> &mut Self {
169        self.driver.fetch_size = fetch_size;
170        self
171    }
172
173    #[must_use]
174    pub fn encryption_enabled(&self) -> bool {
175        self.driver.encryption_enabled
176    }
177
178    #[must_use]
179    pub fn statement_cache_capacity(&self) -> NonZeroUsize {
180        self.driver.statement_cache_capacity
181    }
182
183    pub(crate) fn needs_send(&self) -> bool {
184        self.driver.needs_send
185    }
186
187    pub(crate) fn set_needs_send(&mut self, flag: bool) -> &mut Self {
188        self.driver.needs_send = flag;
189        self
190    }
191
192    pub(crate) fn read_write(&self) -> &ExaRwAttributes<'static> {
193        &self.read_write
194    }
195
196    pub(crate) fn update(&mut self, other: ExaAttributesOpt) {
197        macro_rules! other_or_prev {
198            ($kind:tt, $field:tt) => {
199                if let Some(new) = other.$field {
200                    self.$kind.$field = new.into();
201                }
202            };
203        }
204
205        self.read_write.current_schema = other.current_schema.map(From::from);
206
207        other_or_prev!(read_write, autocommit);
208        other_or_prev!(read_write, feedback_interval);
209        other_or_prev!(read_write, numeric_characters);
210        other_or_prev!(read_write, query_timeout);
211        other_or_prev!(read_write, snapshot_transactions_enabled);
212        other_or_prev!(read_write, timestamp_utc_enabled);
213        other_or_prev!(read_only, compression_enabled);
214        other_or_prev!(read_only, date_format);
215        other_or_prev!(read_only, date_language);
216        other_or_prev!(read_only, datetime_format);
217        other_or_prev!(read_only, default_like_escape_character);
218        other_or_prev!(read_only, timezone);
219        other_or_prev!(read_only, timezone_behavior);
220    }
221}
222
223/// Database read-write attributes.
224///
225/// The lifetime parameter and usage of [`Cow`] is to support embedding these attributes in a
226/// [`crate::connection::websocket::request::ExaLoginRequest`].
227#[derive(Clone, Debug, Serialize)]
228#[serde(rename_all = "camelCase")]
229pub struct ExaRwAttributes<'a> {
230    // The attribute can only change the open schema, it cannot close it, hence the skip. However,
231    // if no schema is currently open Exasol returns `null`.
232    #[serde(skip_serializing_if = "Option::is_none")]
233    current_schema: Option<Cow<'a, str>>,
234    autocommit: bool,
235    feedback_interval: u64,
236    numeric_characters: Cow<'a, str>,
237    query_timeout: u64,
238    snapshot_transactions_enabled: bool,
239    timestamp_utc_enabled: bool,
240}
241
242impl<'a> ExaRwAttributes<'a> {
243    pub(crate) fn new(
244        current_schema: Option<Cow<'a, str>>,
245        feedback_interval: u64,
246        query_timeout: u64,
247    ) -> Self {
248        Self {
249            current_schema,
250            feedback_interval,
251            query_timeout,
252            ..Default::default()
253        }
254    }
255}
256
257impl<'a> Default for ExaRwAttributes<'a> {
258    fn default() -> Self {
259        Self {
260            autocommit: true,
261            current_schema: None,
262            feedback_interval: 1,
263            numeric_characters: Cow::Owned(".,".into()),
264            query_timeout: 0,
265            snapshot_transactions_enabled: false,
266            timestamp_utc_enabled: false,
267        }
268    }
269}
270
271#[derive(Clone, Debug)]
272struct ExaRoAttributes {
273    compression_enabled: bool,
274    date_format: String,
275    date_language: String,
276    datetime_format: String,
277    default_like_escape_character: String,
278    timezone: String,
279    timezone_behavior: String,
280}
281
282impl ExaRoAttributes {
283    fn new(compression_enabled: bool) -> Self {
284        Self {
285            compression_enabled,
286            date_format: "YYYY-MM-DD".to_owned(),
287            date_language: "ENG".to_owned(),
288            datetime_format: "YYYY-MM-DD HH24:MI:SS.FF6".to_owned(),
289            default_like_escape_character: "\\".to_owned(),
290            timezone: "UNIVERSAL".to_owned(),
291            timezone_behavior: "INVALID SHIFT AMBIGUOUS ST".to_owned(),
292        }
293    }
294}
295
296#[derive(Clone, Debug)]
297struct ExaDriverAttributes {
298    // This is technically a read-only attribute, but Exasol doesn't seem to correctly set or even
299    // return it. We therefore control it manually.
300    open_transaction: bool,
301    needs_send: bool,
302    fetch_size: usize,
303    encryption_enabled: bool,
304    statement_cache_capacity: NonZeroUsize,
305}
306
307impl ExaDriverAttributes {
308    fn new(
309        fetch_size: usize,
310        encryption_enabled: bool,
311        statement_cache_capacity: NonZeroUsize,
312    ) -> Self {
313        Self {
314            open_transaction: false,
315            needs_send: false,
316            fetch_size,
317            encryption_enabled,
318            statement_cache_capacity,
319        }
320    }
321}
322
323/// Helper type representing attributes returned by Exasol.
324///
325/// While [`ExaAttributes`] are stored by connections and sent to the database, this type is used
326/// for deserialization of the attributes that Exasol sends to us.
327///
328/// The returned attributes are then used to update a connection's [`ExaAttributes`].
329#[derive(Debug, Default, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct ExaAttributesOpt {
332    // ##########################################################
333    // ############# Database read-write attributes #############
334    // ##########################################################
335    autocommit: Option<bool>,
336    #[serde(default)]
337    #[serde(deserialize_with = "ExaAttributesOpt::deserialize_current_schema")]
338    current_schema: Option<String>,
339    feedback_interval: Option<u64>,
340    numeric_characters: Option<String>,
341    query_timeout: Option<u64>,
342    snapshot_transactions_enabled: Option<bool>,
343    timestamp_utc_enabled: Option<bool>,
344    // ##########################################################
345    // ############# Database read-only attributes ##############
346    // ##########################################################
347    compression_enabled: Option<bool>,
348    date_format: Option<String>,
349    date_language: Option<String>,
350    datetime_format: Option<String>,
351    default_like_escape_character: Option<String>,
352    timezone: Option<String>,
353    timezone_behavior: Option<String>,
354}
355
356impl ExaAttributesOpt {
357    /// Helper function because Exasol returns an empty string if no schema was selected.
358    fn deserialize_current_schema<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
359    where
360        D: Deserializer<'de>,
361    {
362        let Some(value) = Option::deserialize(deserializer)? else {
363            return Ok(None);
364        };
365
366        Ok(String::is_empty(&value).not().then_some(value))
367    }
368}