diesel/connection/
mod.rs

1//! Types related to database connections
2
3pub(crate) mod instrumentation;
4#[cfg(all(
5    not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"),
6    any(feature = "sqlite", feature = "postgres", feature = "mysql")
7))]
8pub(crate) mod statement_cache;
9#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
10pub mod statement_cache;
11mod transaction_manager;
12
13use crate::backend::Backend;
14use crate::expression::QueryMetadata;
15use crate::query_builder::{Query, QueryFragment, QueryId};
16use crate::result::*;
17use crate::sql_types::TypeMetadata;
18use std::fmt::Debug;
19
20#[doc(inline)]
21pub use self::instrumentation::{
22    get_default_instrumentation, set_default_instrumentation, DebugQuery, Instrumentation,
23    InstrumentationEvent,
24};
25#[doc(inline)]
26pub use self::transaction_manager::{
27    AnsiTransactionManager, InTransactionStatus, TransactionDepthChange, TransactionManager,
28    TransactionManagerStatus, ValidTransactionManagerStatus,
29};
30
31#[diesel_derives::__diesel_public_if(
32    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
33)]
34pub(crate) use self::private::ConnectionSealed;
35
36#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
37pub use self::private::MultiConnectionHelper;
38
39#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
40pub use self::instrumentation::{DynInstrumentation, StrQueryHelper};
41
42#[cfg(all(
43    not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"),
44    any(feature = "sqlite", feature = "postgres", feature = "mysql")
45))]
46pub(crate) use self::private::MultiConnectionHelper;
47
48/// Set cache size for a connection
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum CacheSize {
52    /// Caches all queries if possible
53    Unbounded,
54    /// Disable statement cache
55    Disabled,
56}
57
58/// Perform simple operations on a backend.
59///
60/// You should likely use [`Connection`] instead.
61pub trait SimpleConnection {
62    /// Execute multiple SQL statements within the same string.
63    ///
64    /// This function is used to execute migrations,
65    /// which may contain more than one SQL statement.
66    fn batch_execute(&mut self, query: &str) -> QueryResult<()>;
67}
68
69#[doc(hidden)]
70#[cfg(all(feature = "with-deprecated", not(feature = "without-deprecated")))]
71#[deprecated(note = "Directly use `LoadConnection::Cursor` instead")]
72pub type LoadRowIter<'conn, 'query, C, DB, B = DefaultLoadingMode> =
73    <C as self::private::ConnectionHelperType<DB, B>>::Cursor<'conn, 'query>;
74
75/// A connection to a database
76///
77/// This trait represents a database connection. It can be used to query the database through
78/// the query dsl provided by diesel, custom extensions or raw sql queries.
79///
80/// # Implementing a custom connection
81///
82/// There are several reasons why you would want to implement a custom connection implementation:
83///
84/// * To wrap an existing connection for instrumentation purposes
85/// * To use a different underlying library to provide a connection implementation
86/// for already existing backends.
87/// * To add support for an unsupported database system
88///
89/// Implementing a `Connection` in a third party crate requires
90/// enabling the
91/// `i-implement-a-third-party-backend-and-opt-into-breaking-changes`
92/// crate feature which grants access to some of diesel's implementation details.
93///
94///
95/// ## Wrapping an existing connection impl
96///
97/// Wrapping an existing connection allows you to customize the implementation to
98/// add additional functionality, like for example instrumentation. For this use case
99/// you only need to implement `Connection`, [`LoadConnection`] and all super traits.
100/// You should forward any method call to the wrapped connection type.
101/// It is **important** to also forward any method where diesel provides a
102/// default implementation, as the wrapped connection implementation may
103/// contain a customized implementation.
104///
105/// To allow the integration of your new connection type with other diesel features
106#[cfg_attr(
107    feature = "r2d2",
108    doc = "it may be useful to also implement [`R2D2Connection`](crate::r2d2::R2D2Connection)"
109)]
110#[cfg_attr(
111    not(feature = "r2d2"),
112    doc = "it may be useful to also implement `R2D2Connection`"
113)]
114/// and [`MigrationConnection`](crate::migration::MigrationConnection).
115///
116/// ## Provide a new connection implementation for an existing backend
117///
118/// Implementing a new connection based on an existing backend can enable the usage of
119/// other methods to connect to the database. One example here would be to replace
120/// the official diesel provided connection implementations with an implementation
121/// based on a pure rust connection crate.
122///
123/// **It's important to use prepared statements to implement the following methods:**
124/// * [`LoadConnection::load`]
125/// * [`Connection::execute_returning_count`]
126///
127/// For performance reasons it may also be meaningful to cache already prepared statements.
128#[cfg_attr(
129    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
130    doc = "See [`StatementCache`](self::statement_cache::StatementCache)"
131)]
132#[cfg_attr(
133    not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"),
134    doc = "See `StatementCache`"
135)]
136/// for a helper type to implement prepared statement caching.
137#[cfg_attr(
138    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
139    doc = "The [statement_cache](self::statement_cache)"
140)]
141#[cfg_attr(
142    not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"),
143    doc = "The statement_cache"
144)]
145/// module documentation contains details about efficient prepared statement caching
146/// based on diesels query builder.
147///
148/// It is required to implement at least the following parts:
149///
150/// * A row type that describes how to receive values form a database row.
151///   This type needs to implement [`Row`](crate::row::Row)
152/// * A field type that describes a database field value.
153///   This type needs to implement [`Field`](crate::row::Field)
154/// * A connection type that wraps the connection +
155///   the necessary state management.
156/// * Maybe a [`TransactionManager`] implementation matching
157///  the interface provided by the database connection crate.
158///  Otherwise the implementation used by the corresponding
159///  `Connection` in diesel can be reused.
160///
161/// To allow the integration of your new connection type with other diesel features
162#[cfg_attr(
163    feature = "r2d2",
164    doc = "it may be useful to also implement [`R2D2Connection`](crate::r2d2::R2D2Connection)"
165)]
166#[cfg_attr(
167    not(feature = "r2d2"),
168    doc = "it may be useful to also implement `R2D2Connection`"
169)]
170/// and [`MigrationConnection`](crate::migration::MigrationConnection).
171///
172/// The exact implementation of the `Connection` trait depends on the interface provided
173/// by the connection crate/library. A struct implementing `Connection` should
174#[cfg_attr(
175    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
176    doc = "likely contain a [`StatementCache`](self::statement_cache::StatementCache)"
177)]
178#[cfg_attr(
179    not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"),
180    doc = "likely contain a `StatementCache`"
181)]
182/// to cache prepared statements efficiently.
183///
184/// As implementations differ significantly between the supported backends
185/// we cannot give a one for all description here. Generally it's likely a
186/// good idea to follow the implementation of the corresponding connection
187/// in diesel at a high level to gain some idea how to implement your
188/// custom implementation.
189///
190/// ## Implement support for an unsupported database system
191///
192/// Additionally to anything mentioned in the previous section the following steps are required:
193///
194/// * Implement a custom backend type. See the documentation of [`Backend`] for details
195/// * Implement appropriate [`FromSql`](crate::deserialize::FromSql)/
196/// [`ToSql`](crate::serialize::ToSql) conversions.
197/// At least the following impls should be considered:
198///     * `i16`: `FromSql<SmallInt, YourBackend>`
199///     * `i32`: `FromSql<Integer, YourBackend>`
200///     * `i64`: `FromSql<BigInt, YourBackend>`
201///     * `f32`: `FromSql<Float, YourBackend>`
202///     * `f64`: `FromSql<Double, YourBackend>`
203///     * `bool`: `FromSql<Bool, YourBackend>`
204///     * `String`: `FromSql<Text, YourBackend>`
205///     * `Vec<u8>`: `FromSql<Binary, YourBackend>`
206///     * `i16`: `ToSql<SmallInt, YourBackend>`
207///     * `i32`: `ToSql<Integer, YourBackend>`
208///     * `i64`: `ToSql<BigInt, YourBackend>`
209///     * `f32`: `ToSql<Float, YourBackend>`
210///     * `f64`: `ToSql<Double, YourBackend>`
211///     * `bool`: `ToSql<Bool, YourBackend>`
212///     * `String`: `ToSql<Text, YourBackend>`
213///     * `Vec<u8>`: `ToSql<Binary, YourBackend>`
214/// * Maybe a [`TransactionManager`] implementation matching
215///  the interface provided by the database connection crate.
216///  Otherwise the implementation used by the corresponding
217///  `Connection` in diesel can be reused.
218///
219/// As these implementations will vary depending on the backend being used,
220/// we cannot give concrete examples here. We recommend looking at our existing
221/// implementations to see how you can implement your own connection.
222pub trait Connection: SimpleConnection + Sized + Send
223where
224    // This trait bound is there so that implementing a new connection is
225    // gated behind the `i-implement-a-third-party-backend-and-opt-into-breaking-changes`
226    // feature flag
227    Self: ConnectionSealed,
228{
229    /// The backend this type connects to
230    type Backend: Backend;
231
232    /// The transaction manager implementation used by this connection
233    #[diesel_derives::__diesel_public_if(
234        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
235    )]
236    type TransactionManager: TransactionManager<Self>;
237
238    /// Establishes a new connection to the database
239    ///
240    /// The argument to this method and the method's behavior varies by backend.
241    /// See the documentation for that backend's connection class
242    /// for details about what it accepts and how it behaves.
243    fn establish(database_url: &str) -> ConnectionResult<Self>;
244
245    /// Executes the given function inside of a database transaction
246    ///
247    /// This function executes the provided closure `f` inside a database
248    /// transaction. If there is already an open transaction for the current
249    /// connection savepoints will be used instead. The connection is committed if
250    /// the closure returns `Ok(_)`, it will be rolled back if it returns `Err(_)`.
251    /// For both cases the original result value will be returned from this function.
252    ///
253    /// If the transaction fails to commit due to a `SerializationFailure` or a
254    /// `ReadOnlyTransaction` a rollback will be attempted.
255    /// If the rollback fails, the error will be returned in a
256    /// [`Error::RollbackErrorOnCommit`],
257    /// from which you will be able to extract both the original commit error and
258    /// the rollback error.
259    /// In addition, the connection will be considered broken
260    /// as it contains a uncommitted unabortable open transaction. Any further
261    /// interaction with the transaction system will result in an returned error
262    /// in this case.
263    ///
264    /// If the closure returns an `Err(_)` and the rollback fails the function
265    /// will return that rollback error directly, and the transaction manager will
266    /// be marked as broken as it contains a uncommitted unabortable open transaction.
267    ///
268    /// If a nested transaction fails to release the corresponding savepoint
269    /// the error will be returned directly.
270    ///
271    /// # Example
272    ///
273    /// ```rust
274    /// # include!("../doctest_setup.rs");
275    /// use diesel::result::Error;
276    ///
277    /// # fn main() {
278    /// #     run_test().unwrap();
279    /// # }
280    /// #
281    /// # fn run_test() -> QueryResult<()> {
282    /// #     use schema::users::dsl::*;
283    /// #     let conn = &mut establish_connection();
284    /// conn.transaction::<_, Error, _>(|conn| {
285    ///     diesel::insert_into(users)
286    ///         .values(name.eq("Ruby"))
287    ///         .execute(conn)?;
288    ///
289    ///     let all_names = users.select(name).load::<String>(conn)?;
290    ///     assert_eq!(vec!["Sean", "Tess", "Ruby"], all_names);
291    ///
292    ///     Ok(())
293    /// })?;
294    ///
295    /// conn.transaction::<(), _, _>(|conn| {
296    ///     diesel::insert_into(users)
297    ///         .values(name.eq("Pascal"))
298    ///         .execute(conn)?;
299    ///
300    ///     let all_names = users.select(name).load::<String>(conn)?;
301    ///     assert_eq!(vec!["Sean", "Tess", "Ruby", "Pascal"], all_names);
302    ///
303    ///     // If we want to roll back the transaction, but don't have an
304    ///     // actual error to return, we can return `RollbackTransaction`.
305    ///     Err(Error::RollbackTransaction)
306    /// });
307    ///
308    /// let all_names = users.select(name).load::<String>(conn)?;
309    /// assert_eq!(vec!["Sean", "Tess", "Ruby"], all_names);
310    /// #     Ok(())
311    /// # }
312    /// ```
313    fn transaction<T, E, F>(&mut self, f: F) -> Result<T, E>
314    where
315        F: FnOnce(&mut Self) -> Result<T, E>,
316        E: From<Error>,
317    {
318        Self::TransactionManager::transaction(self, f)
319    }
320
321    /// Creates a transaction that will never be committed. This is useful for
322    /// tests. Panics if called while inside of a transaction or
323    /// if called with a connection containing a broken transaction
324    fn begin_test_transaction(&mut self) -> QueryResult<()> {
325        match Self::TransactionManager::transaction_manager_status_mut(self) {
326            TransactionManagerStatus::Valid(valid_status) => {
327                assert_eq!(None, valid_status.transaction_depth())
328            }
329            TransactionManagerStatus::InError => panic!("Transaction manager in error"),
330        };
331        Self::TransactionManager::begin_transaction(self)?;
332        // set the test transaction flag
333        // to prevent that this connection gets dropped in connection pools
334        // Tests commonly set the poolsize to 1 and use `begin_test_transaction`
335        // to prevent modifications to the schema
336        Self::TransactionManager::transaction_manager_status_mut(self).set_test_transaction_flag();
337        Ok(())
338    }
339
340    /// Executes the given function inside a transaction, but does not commit
341    /// it. Panics if the given function returns an error.
342    ///
343    /// # Example
344    ///
345    /// ```rust
346    /// # include!("../doctest_setup.rs");
347    /// use diesel::result::Error;
348    ///
349    /// # fn main() {
350    /// #     run_test().unwrap();
351    /// # }
352    /// #
353    /// # fn run_test() -> QueryResult<()> {
354    /// #     use schema::users::dsl::*;
355    /// #     let conn = &mut establish_connection();
356    /// conn.test_transaction::<_, Error, _>(|conn| {
357    ///     diesel::insert_into(users)
358    ///         .values(name.eq("Ruby"))
359    ///         .execute(conn)?;
360    ///
361    ///     let all_names = users.select(name).load::<String>(conn)?;
362    ///     assert_eq!(vec!["Sean", "Tess", "Ruby"], all_names);
363    ///
364    ///     Ok(())
365    /// });
366    ///
367    /// // Even though we returned `Ok`, the transaction wasn't committed.
368    /// let all_names = users.select(name).load::<String>(conn)?;
369    /// assert_eq!(vec!["Sean", "Tess"], all_names);
370    /// #     Ok(())
371    /// # }
372    /// ```
373    fn test_transaction<T, E, F>(&mut self, f: F) -> T
374    where
375        F: FnOnce(&mut Self) -> Result<T, E>,
376        E: Debug,
377    {
378        let mut user_result = None;
379        let _ = self.transaction::<(), _, _>(|conn| {
380            user_result = f(conn).ok();
381            Err(Error::RollbackTransaction)
382        });
383        user_result.expect("Transaction did not succeed")
384    }
385
386    /// Execute a single SQL statements given by a query and return
387    /// number of affected rows
388    #[diesel_derives::__diesel_public_if(
389        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
390    )]
391    fn execute_returning_count<T>(&mut self, source: &T) -> QueryResult<usize>
392    where
393        T: QueryFragment<Self::Backend> + QueryId;
394
395    /// Get access to the current transaction state of this connection
396    ///
397    /// This function should be used from [`TransactionManager`] to access
398    /// internally required state.
399    #[diesel_derives::__diesel_public_if(
400        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
401    )]
402    fn transaction_state(
403        &mut self,
404    ) -> &mut <Self::TransactionManager as TransactionManager<Self>>::TransactionStateData;
405
406    /// Get the instrumentation instance stored in this connection
407    #[diesel_derives::__diesel_public_if(
408        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
409    )]
410    fn instrumentation(&mut self) -> &mut dyn Instrumentation;
411
412    /// Set a specific [`Instrumentation`] implementation for this connection
413    fn set_instrumentation(&mut self, instrumentation: impl Instrumentation);
414
415    /// Set the prepared statement cache size to [`CacheSize`] for this connection
416    fn set_prepared_statement_cache_size(&mut self, size: CacheSize);
417}
418
419/// The specific part of a [`Connection`] which actually loads data from the database
420///
421/// This is a separate trait to allow connection implementations to specify
422/// different loading modes via the generic parameter.
423pub trait LoadConnection<B = DefaultLoadingMode>: Connection {
424    /// The cursor type returned by [`LoadConnection::load`]
425    ///
426    /// Users should handle this as opaque type that implements [`Iterator`]
427    type Cursor<'conn, 'query>: Iterator<
428        Item = QueryResult<<Self as LoadConnection<B>>::Row<'conn, 'query>>,
429    >
430    where
431        Self: 'conn;
432
433    /// The row type used as [`Iterator::Item`] for the iterator implementation
434    /// of [`LoadConnection::Cursor`]
435    type Row<'conn, 'query>: crate::row::Row<'conn, Self::Backend>
436    where
437        Self: 'conn;
438
439    /// Executes a given query and returns any requested values
440    ///
441    /// This function executes a given query and returns the
442    /// query result as given by the database. **Normal users
443    /// should not use this function**. Use
444    /// [`QueryDsl::load`](crate::QueryDsl) instead.
445    ///
446    /// This function is useful for people trying to build an alternative
447    /// dsl on top of diesel. It returns an [`impl Iterator<Item = QueryResult<&impl Row<Self::Backend>>`](Iterator).
448    /// This type can be used to iterate over all rows returned by the database.
449    #[diesel_derives::__diesel_public_if(
450        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
451    )]
452    fn load<'conn, 'query, T>(
453        &'conn mut self,
454        source: T,
455    ) -> QueryResult<Self::Cursor<'conn, 'query>>
456    where
457        T: Query + QueryFragment<Self::Backend> + QueryId + 'query,
458        Self::Backend: QueryMetadata<T::SqlType>;
459}
460
461/// Describes a connection with an underlying [`crate::sql_types::TypeMetadata::MetadataLookup`]
462#[diesel_derives::__diesel_public_if(
463    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
464)]
465pub trait WithMetadataLookup: Connection {
466    /// Retrieves the underlying metadata lookup
467    fn metadata_lookup(&mut self) -> &mut <Self::Backend as TypeMetadata>::MetadataLookup;
468}
469
470/// A variant of the [`Connection`](trait.Connection.html) trait that is
471/// usable with dynamic dispatch
472///
473/// If you are looking for a way to use pass database connections
474/// for different database backends around in your application
475/// this trait won't help you much. Normally you should only
476/// need to use this trait if you are interacting with a connection
477/// passed to a [`Migration`](../migration/trait.Migration.html)
478pub trait BoxableConnection<DB: Backend>: SimpleConnection + std::any::Any {
479    /// Maps the current connection to `std::any::Any`
480    #[diesel_derives::__diesel_public_if(
481        feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"
482    )]
483    fn as_any(&self) -> &dyn std::any::Any;
484
485    #[doc(hidden)]
486    fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
487}
488
489impl<C> BoxableConnection<C::Backend> for C
490where
491    C: Connection + std::any::Any,
492{
493    fn as_any(&self) -> &dyn std::any::Any {
494        self
495    }
496
497    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
498        self
499    }
500}
501
502/// The default loading mode provided by a [`Connection`].
503///
504/// Checkout the documentation of concrete connection types for details about
505/// supported loading modes.
506///
507/// All types implementing [`Connection`] should provide at least
508/// a single [`LoadConnection<DefaultLoadingMode>`](self::LoadConnection)
509/// implementation.
510#[derive(Debug, Copy, Clone)]
511pub struct DefaultLoadingMode;
512
513impl<DB: Backend + 'static> dyn BoxableConnection<DB> {
514    /// Downcast the current connection to a specific connection
515    /// type.
516    ///
517    /// This will return `None` if the underlying
518    /// connection does not match the corresponding
519    /// type, otherwise a reference to the underlying connection is returned
520    pub fn downcast_ref<T>(&self) -> Option<&T>
521    where
522        T: Connection<Backend = DB> + 'static,
523    {
524        self.as_any().downcast_ref::<T>()
525    }
526
527    /// Downcast the current connection to a specific mutable connection
528    /// type.
529    ///
530    /// This will return `None` if the underlying
531    /// connection does not match the corresponding
532    /// type, otherwise a mutable reference to the underlying connection is returned
533    pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
534    where
535        T: Connection<Backend = DB> + 'static,
536    {
537        self.as_any_mut().downcast_mut::<T>()
538    }
539
540    /// Check if the current connection is
541    /// a specific connection type
542    pub fn is<T>(&self) -> bool
543    where
544        T: Connection<Backend = DB> + 'static,
545    {
546        self.as_any().is::<T>()
547    }
548}
549
550// These traits are considered private for different reasons:
551//
552// `ConnectionSealed` to control who can implement `Connection`,
553// so that we can later change the `Connection` trait
554//
555// `MultiConnectionHelper` is a workaround needed for the
556// `MultiConnection` derive. We might stabilize this trait with
557// the corresponding derive
558//
559// `ConnectionHelperType` as a workaround for the `LoadRowIter`
560// type def. That trait should not be used by any user outside of diesel,
561// it purely exists for backward compatibility reasons.
562pub(crate) mod private {
563
564    /// This trait restricts who can implement `Connection`
565    #[cfg_attr(
566        docsrs,
567        doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
568    )]
569    pub trait ConnectionSealed {}
570
571    /// This trait provides helper methods to convert a database lookup type
572    /// to/from an `std::any::Any` reference. This is used internally by the `#[derive(MultiConnection)]`
573    /// implementation
574    #[cfg_attr(
575        docsrs,
576        doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
577    )]
578    pub trait MultiConnectionHelper: super::Connection {
579        /// Convert the lookup type to any
580        fn to_any<'a>(
581            lookup: &mut <Self::Backend as crate::sql_types::TypeMetadata>::MetadataLookup,
582        ) -> &mut (dyn std::any::Any + 'a);
583
584        /// Get the lookup type from any
585        fn from_any(
586            lookup: &mut dyn std::any::Any,
587        ) -> Option<&mut <Self::Backend as crate::sql_types::TypeMetadata>::MetadataLookup>;
588    }
589
590    // These impls are only there for backward compatibility reasons
591    // Remove them on the next breaking release
592    #[allow(unreachable_pub)] // must be pub for the type def using this trait
593    #[cfg(all(feature = "with-deprecated", not(feature = "without-deprecated")))]
594    pub trait ConnectionHelperType<DB, B>: super::LoadConnection<B, Backend = DB> {
595        type Cursor<'conn, 'query>
596        where
597            Self: 'conn;
598    }
599    #[cfg(all(feature = "with-deprecated", not(feature = "without-deprecated")))]
600    impl<T, B> ConnectionHelperType<T::Backend, B> for T
601    where
602        T: super::LoadConnection<B>,
603    {
604        type Cursor<'conn, 'query>
605            = <T as super::LoadConnection<B>>::Cursor<'conn, 'query>
606        where
607            T: 'conn;
608    }
609}