Skip to main content

sqlmodel_core/
connection.rs

1//! Database connection traits.
2//!
3//! This module defines the core abstractions for database connections:
4//!
5//! - [`Connection`] - Main trait for executing queries and managing transactions
6//! - [`Transaction`] - Trait for transactional operations with savepoint support
7//! - [`IsolationLevel`] - SQL transaction isolation levels
8//! - [`PreparedStatement`] - Pre-compiled statement for efficient repeated execution
9//!
10//! All operations integrate with asupersync's structured concurrency via `Cx` context
11//! for proper cancellation and timeout handling.
12
13use crate::error::Result;
14use crate::row::Row;
15use crate::value::Value;
16use asupersync::{Cx, Outcome};
17
18/// Transaction isolation level.
19///
20/// Defines the degree to which one transaction must be isolated from
21/// resource or data modifications made by other concurrent transactions.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum IsolationLevel {
24    /// Read uncommitted: Transactions can see uncommitted changes from others.
25    /// This is the lowest isolation level, providing minimal guarantees.
26    /// Use with caution - dirty reads, non-repeatable reads, and phantoms possible.
27    ReadUncommitted,
28
29    /// Read committed: Transactions only see committed changes from others.
30    /// This is the default for PostgreSQL. Prevents dirty reads but allows
31    /// non-repeatable reads and phantoms.
32    #[default]
33    ReadCommitted,
34
35    /// Repeatable read: Transactions see a consistent snapshot of the database.
36    /// Prevents dirty reads and non-repeatable reads, but phantoms are possible
37    /// in some databases (though not in PostgreSQL).
38    RepeatableRead,
39
40    /// Serializable: Transactions appear to execute sequentially.
41    /// The highest isolation level, providing complete isolation but potentially
42    /// requiring retries due to serialization failures.
43    Serializable,
44}
45
46impl IsolationLevel {
47    /// Get the SQL syntax for this isolation level.
48    #[must_use]
49    pub const fn as_sql(&self) -> &'static str {
50        match self {
51            IsolationLevel::ReadUncommitted => "READ UNCOMMITTED",
52            IsolationLevel::ReadCommitted => "READ COMMITTED",
53            IsolationLevel::RepeatableRead => "REPEATABLE READ",
54            IsolationLevel::Serializable => "SERIALIZABLE",
55        }
56    }
57}
58
59/// A prepared statement for repeated execution.
60///
61/// Prepared statements are pre-compiled by the database, allowing efficient
62/// repeated execution with different parameter values. They also help prevent
63/// SQL injection since parameters are handled separately from the SQL text.
64#[derive(Debug, Clone)]
65pub struct PreparedStatement {
66    /// Unique identifier for this prepared statement (driver-specific)
67    id: u64,
68    /// The original SQL text
69    sql: String,
70    /// Number of expected parameters
71    param_count: usize,
72    /// Column information for result rows (if available)
73    columns: Option<Vec<String>>,
74}
75
76impl PreparedStatement {
77    /// Create a new prepared statement.
78    ///
79    /// This is typically called by the driver, not by users directly.
80    #[must_use]
81    pub fn new(id: u64, sql: String, param_count: usize) -> Self {
82        Self {
83            id,
84            sql,
85            param_count,
86            columns: None,
87        }
88    }
89
90    /// Create a prepared statement with column information.
91    #[must_use]
92    pub fn with_columns(id: u64, sql: String, param_count: usize, columns: Vec<String>) -> Self {
93        Self {
94            id,
95            sql,
96            param_count,
97            columns: Some(columns),
98        }
99    }
100
101    /// Get the statement ID.
102    #[must_use]
103    pub const fn id(&self) -> u64 {
104        self.id
105    }
106
107    /// Get the original SQL text.
108    #[must_use]
109    pub fn sql(&self) -> &str {
110        &self.sql
111    }
112
113    /// Get the expected number of parameters.
114    #[must_use]
115    pub const fn param_count(&self) -> usize {
116        self.param_count
117    }
118
119    /// Get the column information, if available.
120    #[must_use]
121    pub fn columns(&self) -> Option<&[String]> {
122        self.columns.as_deref()
123    }
124
125    /// Check if the provided parameters match the expected count.
126    #[must_use]
127    pub fn validate_params(&self, params: &[Value]) -> bool {
128        params.len() == self.param_count
129    }
130}
131
132/// A database connection capable of executing queries.
133///
134/// All operations are async and take a `Cx` context for cancellation/timeout support.
135/// Implementations must be `Send + Sync` for use across async boundaries.
136///
137/// # Transaction Support
138///
139/// Use [`begin`](Connection::begin) or [`begin_with`](Connection::begin_with) to
140/// start transactions. Transactions must be explicitly committed or rolled back.
141///
142/// # Example
143///
144/// ```rust,ignore
145/// // Execute a simple query
146/// let rows = conn.query(&cx, "SELECT * FROM users WHERE id = $1", &[Value::Int(1)]).await?;
147///
148/// // Use a transaction
149/// let mut tx = conn.begin(&cx).await?;
150/// tx.execute(&cx, "INSERT INTO logs (msg) VALUES ($1)", &[Value::Text("action".into())]).await?;
151/// tx.commit(&cx).await?;
152/// ```
153/// SQL dialect enumeration for cross-database compatibility.
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
155pub enum Dialect {
156    /// PostgreSQL dialect (uses $1, $2 placeholders)
157    #[default]
158    Postgres,
159    /// SQLite dialect (uses ?1, ?2 placeholders)
160    Sqlite,
161    /// MySQL dialect (uses ? placeholders)
162    Mysql,
163}
164
165impl Dialect {
166    /// Generate a placeholder for the given parameter index (1-based).
167    pub fn placeholder(self, index: usize) -> String {
168        match self {
169            Dialect::Postgres => format!("${index}"),
170            Dialect::Sqlite => format!("?{index}"),
171            Dialect::Mysql => "?".to_string(),
172        }
173    }
174
175    /// Get the string concatenation operator for this dialect.
176    pub const fn concat_op(self) -> &'static str {
177        match self {
178            Dialect::Postgres | Dialect::Sqlite => "||",
179            Dialect::Mysql => "", // MySQL uses CONCAT() function
180        }
181    }
182
183    /// Check if this dialect supports ILIKE.
184    pub const fn supports_ilike(self) -> bool {
185        matches!(self, Dialect::Postgres)
186    }
187
188    /// Quote an identifier for this dialect.
189    ///
190    /// Properly escapes embedded quote characters by doubling them:
191    /// - For Postgres/SQLite: `"` becomes `""`
192    /// - For MySQL: `` ` `` becomes ``` `` ```
193    pub fn quote_identifier(self, name: &str) -> String {
194        match self {
195            Dialect::Postgres | Dialect::Sqlite => {
196                let escaped = name.replace('"', "\"\"");
197                format!("\"{escaped}\"")
198            }
199            Dialect::Mysql => {
200                let escaped = name.replace('`', "``");
201                format!("`{escaped}`")
202            }
203        }
204    }
205}
206
207pub trait Connection: Send + Sync {
208    /// The transaction type returned by this connection.
209    type Tx<'conn>: TransactionOps
210    where
211        Self: 'conn;
212
213    /// Get the SQL dialect for this connection.
214    ///
215    /// This is used by query builders to generate dialect-specific SQL.
216    /// Defaults to Postgres for backwards compatibility.
217    fn dialect(&self) -> Dialect {
218        Dialect::Postgres
219    }
220
221    /// Execute a query and return all rows.
222    fn query(
223        &self,
224        cx: &Cx,
225        sql: &str,
226        params: &[Value],
227    ) -> impl Future<Output = Outcome<Vec<Row>, crate::Error>> + Send;
228
229    /// Execute a query and return the first row, if any.
230    fn query_one(
231        &self,
232        cx: &Cx,
233        sql: &str,
234        params: &[Value],
235    ) -> impl Future<Output = Outcome<Option<Row>, crate::Error>> + Send;
236
237    /// Execute a statement (INSERT, UPDATE, DELETE) and return rows affected.
238    fn execute(
239        &self,
240        cx: &Cx,
241        sql: &str,
242        params: &[Value],
243    ) -> impl Future<Output = Outcome<u64, crate::Error>> + Send;
244
245    /// Execute an INSERT and return the last inserted ID.
246    ///
247    /// For PostgreSQL, this typically uses RETURNING to get the inserted ID.
248    /// The exact behavior depends on the driver implementation.
249    fn insert(
250        &self,
251        cx: &Cx,
252        sql: &str,
253        params: &[Value],
254    ) -> impl Future<Output = Outcome<i64, crate::Error>> + Send;
255
256    /// Execute multiple statements in a batch.
257    ///
258    /// Returns the number of rows affected by each statement.
259    /// The statements are executed sequentially but may be optimized
260    /// by the driver for better performance.
261    fn batch(
262        &self,
263        cx: &Cx,
264        statements: &[(String, Vec<Value>)],
265    ) -> impl Future<Output = Outcome<Vec<u64>, crate::Error>> + Send;
266
267    /// Begin a transaction with default isolation level (ReadCommitted).
268    fn begin(&self, cx: &Cx) -> impl Future<Output = Outcome<Self::Tx<'_>, crate::Error>> + Send;
269
270    /// Begin a transaction with a specific isolation level.
271    fn begin_with(
272        &self,
273        cx: &Cx,
274        isolation: IsolationLevel,
275    ) -> impl Future<Output = Outcome<Self::Tx<'_>, crate::Error>> + Send;
276
277    /// Prepare a statement for repeated execution.
278    ///
279    /// Prepared statements are cached by the driver and can be executed
280    /// multiple times with different parameters efficiently.
281    fn prepare(
282        &self,
283        cx: &Cx,
284        sql: &str,
285    ) -> impl Future<Output = Outcome<PreparedStatement, crate::Error>> + Send;
286
287    /// Execute a prepared statement and return all rows.
288    fn query_prepared(
289        &self,
290        cx: &Cx,
291        stmt: &PreparedStatement,
292        params: &[Value],
293    ) -> impl Future<Output = Outcome<Vec<Row>, crate::Error>> + Send;
294
295    /// Execute a prepared statement (INSERT, UPDATE, DELETE) and return rows affected.
296    fn execute_prepared(
297        &self,
298        cx: &Cx,
299        stmt: &PreparedStatement,
300        params: &[Value],
301    ) -> impl Future<Output = Outcome<u64, crate::Error>> + Send;
302
303    /// Check if the connection is still valid by sending a ping.
304    fn ping(&self, cx: &Cx) -> impl Future<Output = Outcome<(), crate::Error>> + Send;
305
306    /// Check if the connection is still valid (alias for ping that returns bool).
307    fn is_valid(&self, cx: &Cx) -> impl Future<Output = bool> + Send {
308        async {
309            match self.ping(cx).await {
310                Outcome::Ok(()) => true,
311                Outcome::Err(_) | Outcome::Cancelled(_) | Outcome::Panicked(_) => false,
312            }
313        }
314    }
315
316    /// Close the connection gracefully.
317    fn close(self, cx: &Cx) -> impl Future<Output = Result<()>> + Send;
318}
319
320/// Trait for transaction operations.
321///
322/// This trait defines the interface for database transactions with
323/// support for savepoints. Transactions must be explicitly committed
324/// or rolled back; dropping without commit triggers automatic rollback.
325///
326/// # Savepoints
327///
328/// Savepoints allow partial rollback within a transaction:
329///
330/// ```rust,ignore
331/// let mut tx = conn.begin(&cx).await?;
332/// tx.execute(&cx, "INSERT INTO t1 (a) VALUES (1)", &[]).await?;
333/// tx.savepoint(&cx, "sp1").await?;
334/// tx.execute(&cx, "INSERT INTO t1 (a) VALUES (2)", &[]).await?;
335/// tx.rollback_to(&cx, "sp1").await?;  // Rollback only the second insert
336/// tx.commit(&cx).await?;  // Only first insert is committed
337/// ```
338pub trait TransactionOps: Send {
339    /// Execute a query within this transaction.
340    fn query(
341        &self,
342        cx: &Cx,
343        sql: &str,
344        params: &[Value],
345    ) -> impl Future<Output = Outcome<Vec<Row>, crate::Error>> + Send;
346
347    /// Execute a query and return the first row, if any.
348    fn query_one(
349        &self,
350        cx: &Cx,
351        sql: &str,
352        params: &[Value],
353    ) -> impl Future<Output = Outcome<Option<Row>, crate::Error>> + Send;
354
355    /// Execute a statement within this transaction.
356    fn execute(
357        &self,
358        cx: &Cx,
359        sql: &str,
360        params: &[Value],
361    ) -> impl Future<Output = Outcome<u64, crate::Error>> + Send;
362
363    /// Create a savepoint within this transaction.
364    ///
365    /// Savepoints allow partial rollback without aborting the entire transaction.
366    fn savepoint(
367        &self,
368        cx: &Cx,
369        name: &str,
370    ) -> impl Future<Output = Outcome<(), crate::Error>> + Send;
371
372    /// Rollback to a previously created savepoint.
373    ///
374    /// All changes made after the savepoint are discarded, but the transaction
375    /// remains active and changes before the savepoint are preserved.
376    fn rollback_to(
377        &self,
378        cx: &Cx,
379        name: &str,
380    ) -> impl Future<Output = Outcome<(), crate::Error>> + Send;
381
382    /// Release a savepoint, making the changes permanent within the transaction.
383    ///
384    /// This frees resources associated with the savepoint but does not commit
385    /// changes to the database (that happens when the transaction commits).
386    fn release(
387        &self,
388        cx: &Cx,
389        name: &str,
390    ) -> impl Future<Output = Outcome<(), crate::Error>> + Send;
391
392    /// Commit the transaction, making all changes permanent.
393    fn commit(self, cx: &Cx) -> impl Future<Output = Outcome<(), crate::Error>> + Send;
394
395    /// Rollback the transaction, discarding all changes.
396    fn rollback(self, cx: &Cx) -> impl Future<Output = Outcome<(), crate::Error>> + Send;
397}
398
399/// A database transaction (concrete implementation).
400///
401/// Transactions provide ACID guarantees and can be committed or rolled back.
402/// If dropped without committing, the transaction is automatically rolled back.
403///
404/// This is a concrete type used by the default transaction implementation.
405/// Driver-specific implementations may use their own types that implement
406/// [`TransactionOps`].
407pub struct Transaction<'conn> {
408    /// The underlying connection
409    conn: &'conn dyn TransactionInternal,
410    /// Whether this transaction has been finalized (committed or rolled back)
411    finalized: bool,
412}
413
414/// Internal trait for transaction operations (object-safe subset).
415///
416/// This trait provides a boxed-future version of TransactionOps for
417/// use with trait objects.
418pub trait TransactionInternal: Send + Sync {
419    /// Execute a query.
420    fn query_internal(
421        &self,
422        cx: &Cx,
423        sql: &str,
424        params: &[Value],
425    ) -> std::pin::Pin<Box<dyn Future<Output = Outcome<Vec<Row>, crate::Error>> + Send + '_>>;
426
427    /// Execute a query and return first row.
428    fn query_one_internal(
429        &self,
430        cx: &Cx,
431        sql: &str,
432        params: &[Value],
433    ) -> std::pin::Pin<Box<dyn Future<Output = Outcome<Option<Row>, crate::Error>> + Send + '_>>;
434
435    /// Execute a statement.
436    fn execute_internal(
437        &self,
438        cx: &Cx,
439        sql: &str,
440        params: &[Value],
441    ) -> std::pin::Pin<Box<dyn Future<Output = Outcome<u64, crate::Error>> + Send + '_>>;
442
443    /// Create a savepoint.
444    fn savepoint_internal(
445        &self,
446        cx: &Cx,
447        name: &str,
448    ) -> std::pin::Pin<Box<dyn Future<Output = Outcome<(), crate::Error>> + Send + '_>>;
449
450    /// Rollback to a savepoint.
451    fn rollback_to_internal(
452        &self,
453        cx: &Cx,
454        name: &str,
455    ) -> std::pin::Pin<Box<dyn Future<Output = Outcome<(), crate::Error>> + Send + '_>>;
456
457    /// Release a savepoint.
458    fn release_internal(
459        &self,
460        cx: &Cx,
461        name: &str,
462    ) -> std::pin::Pin<Box<dyn Future<Output = Outcome<(), crate::Error>> + Send + '_>>;
463
464    /// Commit the transaction.
465    fn commit_internal(
466        &self,
467        cx: &Cx,
468    ) -> std::pin::Pin<Box<dyn Future<Output = Outcome<(), crate::Error>> + Send + '_>>;
469
470    /// Rollback the transaction.
471    fn rollback_internal(
472        &self,
473        cx: &Cx,
474    ) -> std::pin::Pin<Box<dyn Future<Output = Outcome<(), crate::Error>> + Send + '_>>;
475}
476
477impl<'conn> Transaction<'conn> {
478    /// Create a new transaction wrapper.
479    ///
480    /// This is typically called by the driver, not by users directly.
481    pub fn new(conn: &'conn dyn TransactionInternal) -> Self {
482        Self {
483            conn,
484            finalized: false,
485        }
486    }
487
488    /// Check if this transaction has been finalized.
489    #[must_use]
490    pub const fn is_finalized(&self) -> bool {
491        self.finalized
492    }
493}
494
495impl TransactionOps for Transaction<'_> {
496    fn query(
497        &self,
498        cx: &Cx,
499        sql: &str,
500        params: &[Value],
501    ) -> impl Future<Output = Outcome<Vec<Row>, crate::Error>> + Send {
502        self.conn.query_internal(cx, sql, params)
503    }
504
505    fn query_one(
506        &self,
507        cx: &Cx,
508        sql: &str,
509        params: &[Value],
510    ) -> impl Future<Output = Outcome<Option<Row>, crate::Error>> + Send {
511        self.conn.query_one_internal(cx, sql, params)
512    }
513
514    fn execute(
515        &self,
516        cx: &Cx,
517        sql: &str,
518        params: &[Value],
519    ) -> impl Future<Output = Outcome<u64, crate::Error>> + Send {
520        self.conn.execute_internal(cx, sql, params)
521    }
522
523    fn savepoint(
524        &self,
525        cx: &Cx,
526        name: &str,
527    ) -> impl Future<Output = Outcome<(), crate::Error>> + Send {
528        self.conn.savepoint_internal(cx, name)
529    }
530
531    fn rollback_to(
532        &self,
533        cx: &Cx,
534        name: &str,
535    ) -> impl Future<Output = Outcome<(), crate::Error>> + Send {
536        self.conn.rollback_to_internal(cx, name)
537    }
538
539    fn release(
540        &self,
541        cx: &Cx,
542        name: &str,
543    ) -> impl Future<Output = Outcome<(), crate::Error>> + Send {
544        self.conn.release_internal(cx, name)
545    }
546
547    async fn commit(mut self, cx: &Cx) -> Outcome<(), crate::Error> {
548        self.finalized = true;
549        self.conn.commit_internal(cx).await
550    }
551
552    async fn rollback(mut self, cx: &Cx) -> Outcome<(), crate::Error> {
553        self.finalized = true;
554        self.conn.rollback_internal(cx).await
555    }
556}
557
558use std::future::Future;
559
560impl Drop for Transaction<'_> {
561    fn drop(&mut self) {
562        if !self.finalized {
563            // Transaction was not committed/rolled back explicitly.
564            // The actual rollback happens at the protocol level when the
565            // connection detects an unfinalized transaction scope.
566            // We can't do async in drop, so we just mark it here.
567        }
568    }
569}
570
571/// Configuration for database connections.
572#[derive(Debug, Clone)]
573pub struct ConnectionConfig {
574    /// Connection string or URL
575    pub url: String,
576    /// Connection timeout in milliseconds
577    pub connect_timeout_ms: u64,
578    /// Query timeout in milliseconds
579    pub query_timeout_ms: u64,
580    /// SSL mode
581    pub ssl_mode: SslMode,
582    /// Application name for connection identification
583    pub application_name: Option<String>,
584}
585
586/// SSL connection mode.
587#[derive(Debug, Clone, Copy, Default)]
588pub enum SslMode {
589    /// Never use SSL
590    Disable,
591    /// Prefer SSL but allow non-SSL
592    #[default]
593    Prefer,
594    /// Require SSL
595    Require,
596    /// Verify server certificate
597    VerifyCa,
598    /// Verify server certificate and hostname
599    VerifyFull,
600}
601
602impl Default for ConnectionConfig {
603    fn default() -> Self {
604        Self {
605            url: String::new(),
606            connect_timeout_ms: 30_000,
607            query_timeout_ms: 30_000,
608            ssl_mode: SslMode::default(),
609            application_name: None,
610        }
611    }
612}
613
614impl ConnectionConfig {
615    /// Create a new connection config with the given URL.
616    pub fn new(url: impl Into<String>) -> Self {
617        Self {
618            url: url.into(),
619            ..Default::default()
620        }
621    }
622
623    /// Set the connection timeout.
624    pub fn connect_timeout(mut self, ms: u64) -> Self {
625        self.connect_timeout_ms = ms;
626        self
627    }
628
629    /// Set the query timeout.
630    pub fn query_timeout(mut self, ms: u64) -> Self {
631        self.query_timeout_ms = ms;
632        self
633    }
634
635    /// Set the SSL mode.
636    pub fn ssl_mode(mut self, mode: SslMode) -> Self {
637        self.ssl_mode = mode;
638        self
639    }
640
641    /// Set the application name.
642    pub fn application_name(mut self, name: impl Into<String>) -> Self {
643        self.application_name = Some(name.into());
644        self
645    }
646}
647
648#[cfg(test)]
649mod tests {
650    use super::*;
651
652    #[test]
653    fn test_isolation_level_default() {
654        let level = IsolationLevel::default();
655        assert_eq!(level, IsolationLevel::ReadCommitted);
656    }
657
658    #[test]
659    fn test_isolation_level_as_sql() {
660        assert_eq!(IsolationLevel::ReadUncommitted.as_sql(), "READ UNCOMMITTED");
661        assert_eq!(IsolationLevel::ReadCommitted.as_sql(), "READ COMMITTED");
662        assert_eq!(IsolationLevel::RepeatableRead.as_sql(), "REPEATABLE READ");
663        assert_eq!(IsolationLevel::Serializable.as_sql(), "SERIALIZABLE");
664    }
665
666    #[test]
667    fn test_prepared_statement_new() {
668        let stmt = PreparedStatement::new(1, "SELECT * FROM users WHERE id = $1".to_string(), 1);
669        assert_eq!(stmt.id(), 1);
670        assert_eq!(stmt.sql(), "SELECT * FROM users WHERE id = $1");
671        assert_eq!(stmt.param_count(), 1);
672        assert!(stmt.columns().is_none());
673    }
674
675    #[test]
676    fn test_prepared_statement_with_columns() {
677        let stmt = PreparedStatement::with_columns(
678            2,
679            "SELECT id, name FROM users".to_string(),
680            0,
681            vec!["id".to_string(), "name".to_string()],
682        );
683        assert_eq!(stmt.id(), 2);
684        assert_eq!(stmt.param_count(), 0);
685        assert_eq!(
686            stmt.columns(),
687            Some(&["id".to_string(), "name".to_string()][..])
688        );
689    }
690
691    #[test]
692    fn test_prepared_statement_validate_params() {
693        let stmt = PreparedStatement::new(1, "SELECT $1, $2".to_string(), 2);
694
695        assert!(!stmt.validate_params(&[]));
696        assert!(!stmt.validate_params(&[Value::Int(1)]));
697        assert!(stmt.validate_params(&[Value::Int(1), Value::Int(2)]));
698        assert!(!stmt.validate_params(&[Value::Int(1), Value::Int(2), Value::Int(3)]));
699    }
700
701    #[test]
702    fn test_ssl_mode_default() {
703        let mode = SslMode::default();
704        assert!(matches!(mode, SslMode::Prefer));
705    }
706
707    #[test]
708    fn test_connection_config_builder() {
709        let config = ConnectionConfig::new("postgres://localhost/test")
710            .connect_timeout(5000)
711            .query_timeout(10000)
712            .ssl_mode(SslMode::Require)
713            .application_name("test_app");
714
715        assert_eq!(config.url, "postgres://localhost/test");
716        assert_eq!(config.connect_timeout_ms, 5000);
717        assert_eq!(config.query_timeout_ms, 10000);
718        assert!(matches!(config.ssl_mode, SslMode::Require));
719        assert_eq!(config.application_name, Some("test_app".to_string()));
720    }
721
722    #[test]
723    fn test_connection_config_default() {
724        let config = ConnectionConfig::default();
725        assert_eq!(config.url, "");
726        assert_eq!(config.connect_timeout_ms, 30_000);
727        assert_eq!(config.query_timeout_ms, 30_000);
728        assert!(matches!(config.ssl_mode, SslMode::Prefer));
729        assert!(config.application_name.is_none());
730    }
731}