sqlx_exasol_impl/responses/
attributes.rs

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