Skip to main content

rudb_common/
session.rs

1//! What a session has set, for the tables and functions that read a setting back.
2//!
3//! Here for the layer rule and not because a setting is a kind of value, which is the same reason
4//! [`crate::Cancel`] is here. The thing that fills this in is the embedding API at rank 13, which is
5//! the only place that knows what `SET memory_limit` left behind, and the thing that reads it is the
6//! executor at rank 12, where `duckdb_settings()` is built. No two crates in between can see each
7//! other, so the only place both of them can see is the bottom.
8//!
9//! Strings on both sides, rather than a value per setting. A setting is written as text by `SET`,
10//! read back as text by `current_setting()` and printed as text by `duckdb_settings()`, and the one
11//! place the type matters is the `input_type` column, which is a fact about the setting rather than
12//! about the session. Holding a `Value` here would mean the rendering happened twice, once for each
13//! reader, and the two would eventually disagree about how many decimal places a memory limit has.
14
15use std::collections::BTreeMap;
16
17use chrono::{Offset, TimeZone as _, Utc};
18use chrono_tz::Tz;
19
20use crate::Rules;
21
22/// The settings a session has, by name.
23///
24/// Every setting the engine has, not only the ones somebody changed. A reader of this is answering
25/// "what is it now", so a name that is missing means the engine does not have that setting rather
26/// than that it is at its default.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct Session {
29    values: BTreeMap<String, String>,
30    time_zone: Tz,
31    semantics: Semantics,
32    rules: Rules,
33}
34
35/// The meaning-changing session choices consumed while a query is bound.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct Semantics {
38    default_descending: bool,
39    default_null_order: DefaultNullOrder,
40    disable_timestamptz_casts: bool,
41    errors_as_json: bool,
42    integer_division: bool,
43    ieee_floating_point_ops: bool,
44    identifier_case: IdentifierCase,
45    null_on_division_by_zero: bool,
46    order_by_non_integer_literal: bool,
47    regex_match_full: bool,
48    scalar_subquery_error_on_multiple_rows: bool,
49    show_behavior: ShowBehavior,
50    warnings_as_errors: bool,
51}
52
53impl Default for Semantics {
54    fn default() -> Self {
55        Self {
56            default_descending: false,
57            default_null_order: DefaultNullOrder::default(),
58            disable_timestamptz_casts: false,
59            errors_as_json: false,
60            integer_division: false,
61            ieee_floating_point_ops: true,
62            identifier_case: IdentifierCase::Preserve,
63            null_on_division_by_zero: false,
64            order_by_non_integer_literal: false,
65            regex_match_full: false,
66            scalar_subquery_error_on_multiple_rows: true,
67            show_behavior: ShowBehavior::Auto,
68            warnings_as_errors: false,
69        }
70    }
71}
72
73impl Semantics {
74    /// Whether errors are returned as structured JSON.
75    #[must_use]
76    pub fn errors_as_json(self) -> bool {
77        self.errors_as_json
78    }
79    /// How unquoted identifiers are folded while a statement is parsed.
80    #[must_use]
81    pub fn identifier_case(self) -> IdentifierCase {
82        self.identifier_case
83    }
84    /// Whether casts from local timestamps to zoned timestamps are refused.
85    #[must_use]
86    pub fn disable_timestamptz_casts(self) -> bool {
87        self.disable_timestamptz_casts
88    }
89
90    /// Whether floating division and remainder use IEEE answers for zero divisors.
91    #[must_use]
92    pub fn ieee_floating_point_ops(self) -> bool {
93        self.ieee_floating_point_ops
94    }
95
96    /// Whether an order item with no direction is descending.
97    #[must_use]
98    pub fn default_descending(self) -> bool {
99        self.default_descending
100    }
101
102    /// Whether nulls precede values for an unstated placement in this direction.
103    #[must_use]
104    pub fn nulls_first(self, descending: bool) -> bool {
105        match self.default_null_order {
106            DefaultNullOrder::First => true,
107            DefaultNullOrder::Last => false,
108            DefaultNullOrder::Sqlite => !descending,
109            DefaultNullOrder::Postgres => descending,
110        }
111    }
112
113    /// Whether `/` is bound as the integer division operator.
114    #[must_use]
115    pub fn integer_division(self) -> bool {
116        self.integer_division
117    }
118
119    /// Whether a division that would raise on a zero divisor yields null instead.
120    #[must_use]
121    pub fn null_on_division_by_zero(self) -> bool {
122        self.null_on_division_by_zero
123    }
124
125    /// Whether a constant non-integer expression is accepted as a sort key.
126    #[must_use]
127    pub fn order_by_non_integer_literal(self) -> bool {
128        self.order_by_non_integer_literal
129    }
130
131    /// Whether regex match operators require the entire string to match.
132    #[must_use]
133    pub fn regex_match_full(self) -> bool {
134        self.regex_match_full
135    }
136
137    /// Whether a scalar query producing several rows raises an error.
138    #[must_use]
139    pub fn scalar_subquery_error_on_multiple_rows(self) -> bool {
140        self.scalar_subquery_error_on_multiple_rows
141    }
142
143    /// How a bare name following `SHOW` is resolved.
144    #[must_use]
145    pub fn show_behavior(self) -> ShowBehavior {
146        self.show_behavior
147    }
148
149    /// Whether warnings are promoted to errors.
150    #[must_use]
151    pub fn warnings_as_errors(self) -> bool {
152        self.warnings_as_errors
153    }
154}
155
156/// How a session folds identifiers that were not quoted.
157#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
158pub enum IdentifierCase {
159    /// Keep the spelling in the statement.
160    #[default]
161    Preserve,
162    /// Fold ASCII letters to lowercase.
163    Lower,
164    /// Fold ASCII letters to uppercase.
165    Upper,
166}
167
168/// How `SHOW name` chooses between a setting and a table.
169#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
170pub enum ShowBehavior {
171    /// Prefer a table when one exists, then fall back to a setting.
172    #[default]
173    Auto,
174    /// Always read a setting.
175    Setting,
176    /// Always describe a table.
177    Table,
178}
179
180/// How an unstated `NULLS FIRST` or `NULLS LAST` is resolved.
181#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
182pub enum DefaultNullOrder {
183    /// Nulls precede values in both directions.
184    First,
185    /// Nulls follow values in both directions, which is DuckDB's default.
186    #[default]
187    Last,
188    /// Nulls are low, as in SQLite and MySQL.
189    Sqlite,
190    /// Nulls are high, as in PostgreSQL.
191    Postgres,
192}
193
194/// A parsed session time zone, cheap enough to carry beside a prepared expression.
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct SessionTimeZone(Tz);
197
198impl Default for SessionTimeZone {
199    fn default() -> Self {
200        Self(chrono_tz::UTC)
201    }
202}
203
204impl SessionTimeZone {
205    /// The UTC offset in seconds at an instant expressed as Unix microseconds.
206    #[must_use]
207    pub fn offset_seconds_at(self, micros: i64) -> i32 {
208        let seconds = micros.div_euclid(1_000_000);
209        let nanos = u32::try_from(micros.rem_euclid(1_000_000) * 1_000).unwrap_or_default();
210        let Some(utc) = Utc.timestamp_opt(seconds, nanos).single() else { return 0 };
211        self.0.offset_from_utc_datetime(&utc.naive_utc()).fix().local_minus_utc()
212    }
213}
214
215impl Default for Session {
216    fn default() -> Self {
217        Self {
218            values: BTreeMap::new(),
219            time_zone: chrono_tz::UTC,
220            semantics: Semantics::default(),
221            rules: Rules::new(),
222        }
223    }
224}
225
226impl Session {
227    /// A session that knows nothing, which is what a caller with no database behind it has.
228    #[must_use]
229    pub fn new() -> Self {
230        Self::default()
231    }
232
233    /// Records what one setting is now.
234    pub fn set(&mut self, name: &str, value: impl Into<String>) {
235        self.values.insert(name.to_string(), value.into());
236    }
237
238    /// Sets the time zone after it has been validated by the setting layer.
239    pub fn set_time_zone(&mut self, name: &str) {
240        self.time_zone = name.parse().unwrap_or(chrono_tz::UTC);
241    }
242
243    /// The canonical IANA name of the session time zone.
244    #[must_use]
245    pub fn time_zone(&self) -> &str {
246        self.time_zone.name()
247    }
248
249    /// The parsed zone used by prepared expressions.
250    #[must_use]
251    pub fn session_time_zone(&self) -> SessionTimeZone {
252        SessionTimeZone(self.time_zone)
253    }
254
255    /// Sets the direction used by an order item that names none.
256    pub fn set_default_descending(&mut self, descending: bool) {
257        self.semantics.default_descending = descending;
258    }
259
260    /// Sets how an order item with no null placement is resolved.
261    pub fn set_default_null_order(&mut self, order: DefaultNullOrder) {
262        self.semantics.default_null_order = order;
263    }
264
265    /// Sets whether `/` is bound as the integer division operator.
266    pub fn set_integer_division(&mut self, enabled: bool) {
267        self.semantics.integer_division = enabled;
268    }
269
270    /// Sets whether floating division and remainder use IEEE answers for zero divisors.
271    pub fn set_ieee_floating_point_ops(&mut self, enabled: bool) {
272        self.semantics.ieee_floating_point_ops = enabled;
273    }
274
275    /// Sets how unquoted identifiers are folded while a statement is parsed.
276    pub fn set_identifier_case(&mut self, case: IdentifierCase) {
277        self.semantics.identifier_case = case;
278    }
279
280    /// Sets whether division errors caused by a zero divisor become nulls.
281    pub fn set_null_on_division_by_zero(&mut self, enabled: bool) {
282        self.semantics.null_on_division_by_zero = enabled;
283    }
284
285    /// Sets whether a constant non-integer expression is accepted as a sort key.
286    pub fn set_order_by_non_integer_literal(&mut self, enabled: bool) {
287        self.semantics.order_by_non_integer_literal = enabled;
288    }
289
290    /// Sets whether casts from local timestamps to zoned timestamps are refused.
291    pub fn set_disable_timestamptz_casts(&mut self, enabled: bool) {
292        self.semantics.disable_timestamptz_casts = enabled;
293    }
294
295    /// Sets whether errors are returned as structured JSON.
296    pub fn set_errors_as_json(&mut self, enabled: bool) {
297        self.semantics.errors_as_json = enabled;
298    }
299
300    /// Sets whether regex match operators require the entire string to match.
301    pub fn set_regex_match_full(&mut self, enabled: bool) {
302        self.semantics.regex_match_full = enabled;
303    }
304
305    /// Sets whether scalar queries may choose one row from several.
306    pub fn set_scalar_subquery_error_on_multiple_rows(&mut self, enabled: bool) {
307        self.semantics.scalar_subquery_error_on_multiple_rows = enabled;
308    }
309
310    /// Sets how `SHOW name` resolves its name.
311    pub fn set_show_behavior(&mut self, behavior: ShowBehavior) {
312        self.semantics.show_behavior = behavior;
313    }
314
315    /// Sets whether warnings are promoted to errors.
316    pub fn set_warnings_as_errors(&mut self, enabled: bool) {
317        self.semantics.warnings_as_errors = enabled;
318    }
319
320    /// The meaning-changing choices the binder resolves into the plan.
321    #[must_use]
322    pub fn semantics(&self) -> Semantics {
323        self.semantics
324    }
325
326    /// Records which optimization rules the session has turned off.
327    pub fn set_rules(&mut self, rules: Rules) {
328        self.rules = rules;
329    }
330
331    /// Which optimization rules may fire for this statement.
332    ///
333    /// Read by whatever is about to apply one, which is why it rides on the session rather than
334    /// being reached for through the database: the rank that sets it and the ranks that obey it
335    /// cannot see each other.
336    #[must_use]
337    pub fn rules(&self) -> Rules {
338        self.rules
339    }
340
341    /// Whether the bundled time-zone database knows this name.
342    #[must_use]
343    pub fn knows_time_zone(name: &str) -> bool {
344        name.parse::<Tz>().is_ok()
345    }
346
347    /// The UTC offset in seconds at an instant expressed as Unix microseconds.
348    #[must_use]
349    pub fn offset_seconds_at(&self, micros: i64) -> i32 {
350        self.session_time_zone().offset_seconds_at(micros)
351    }
352
353    /// A UTC instant shifted to the wall clock of this session.
354    #[must_use]
355    pub fn local_micros(&self, micros: i64) -> i64 {
356        micros.saturating_add(i64::from(self.offset_seconds_at(micros)) * 1_000_000)
357    }
358
359    /// What that setting is now, and `None` for a name this session has no answer for.
360    #[must_use]
361    pub fn get(&self, name: &str) -> Option<&str> {
362        self.values.get(name).map(String::as_str)
363    }
364
365    /// Every setting and its value, in name order.
366    pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
367        self.values.iter().map(|(name, value)| (name.as_str(), value.as_str()))
368    }
369
370    /// Whether nothing has been recorded.
371    #[must_use]
372    pub fn is_empty(&self) -> bool {
373        self.values.is_empty()
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::Session;
380
381    #[test]
382    fn a_session_hands_back_what_was_put_in_and_says_nothing_about_a_name_it_has_not_got() {
383        let mut session = Session::new();
384        assert!(session.is_empty());
385        session.set("threads", "8");
386        session.set("memory_limit", "1.0 GiB");
387        assert_eq!(session.get("threads"), Some("8"));
388        assert_eq!(session.get("nothing_called_this"), None);
389        // Name order, because the one reader of this is a catalog table that comes out sorted and
390        // sorting it twice would be sorting it once too many.
391        let pairs: Vec<(&str, &str)> = session.iter().collect();
392        assert_eq!(pairs, [("memory_limit", "1.0 GiB"), ("threads", "8")]);
393    }
394}