Skip to main content

mysql_async/queryable/
mod.rs

1// Copyright (c) 2017 Anatoly Ikorsky
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9use futures_util::FutureExt;
10use mysql_common::{
11    constants::{MariadbCapabilities, MAX_PAYLOAD_LEN},
12    io::ParseBuf,
13    proto::{Binary, Text},
14    row::RowDeserializer,
15    value::ServerSide,
16};
17
18use std::{fmt, sync::Arc};
19
20use self::{
21    query_result::QueryResult,
22    stmt::Statement,
23    transaction::{Transaction, TxStatus},
24};
25
26use crate::{
27    conn::routines::{PingRoutine, QueryRoutine},
28    consts::CapabilityFlags,
29    error::*,
30    prelude::{FromRow, StatementLike},
31    query::AsQuery,
32    queryable::query_result::ResultSetMeta,
33    tracing_utils::{LevelInfo, LevelTrace, TracingLevel},
34    BoxFuture, Column, Conn, Connection, Params, ResultSetStream, Row,
35};
36
37pub mod query_result;
38pub mod stmt;
39pub mod transaction;
40
41pub trait Protocol: fmt::Debug + Send + Sync + 'static {
42    /// Returns `ResultSetMeta`, that corresponds to the current protocol.
43    fn result_set_meta(columns: Arc<[Column]>) -> ResultSetMeta;
44    fn read_result_set_row(packet: &[u8], columns: Arc<[Column]>) -> Result<Row>;
45    fn is_last_result_set_packet(capabilities: CapabilityFlags, packet: &[u8]) -> bool {
46        if capabilities.contains(CapabilityFlags::CLIENT_DEPRECATE_EOF) {
47            packet[0] == 0xFE && packet.len() < MAX_PAYLOAD_LEN
48        } else {
49            packet[0] == 0xFE && packet.len() < 8
50        }
51    }
52    fn metadata_skip_possible(conn: &Conn) -> bool;
53}
54
55/// Phantom struct used to specify MySql text protocol.
56#[derive(Debug)]
57pub struct TextProtocol;
58
59/// Phantom struct used to specify MySql binary protocol.
60#[derive(Debug)]
61pub struct BinaryProtocol;
62
63impl Protocol for TextProtocol {
64    fn result_set_meta(columns: Arc<[Column]>) -> ResultSetMeta {
65        ResultSetMeta::Text(columns)
66    }
67
68    fn read_result_set_row(packet: &[u8], columns: Arc<[Column]>) -> Result<Row> {
69        ParseBuf(packet)
70            .parse::<RowDeserializer<ServerSide, Text>>(columns)
71            .map(Into::into)
72            .map_err(Into::into)
73    }
74
75    fn metadata_skip_possible(_conn: &Conn) -> bool {
76        false
77    }
78}
79
80impl Protocol for BinaryProtocol {
81    fn result_set_meta(columns: Arc<[Column]>) -> ResultSetMeta {
82        ResultSetMeta::Binary(columns)
83    }
84
85    fn read_result_set_row(packet: &[u8], columns: Arc<[Column]>) -> Result<Row> {
86        ParseBuf(packet)
87            .parse::<RowDeserializer<ServerSide, Binary>>(columns)
88            .map(Into::into)
89            .map_err(Into::into)
90    }
91
92    fn metadata_skip_possible(conn: &Conn) -> bool {
93        conn.has_mariadb_capabilities(MariadbCapabilities::MARIADB_CLIENT_CACHE_METADATA)
94    }
95}
96
97impl Conn {
98    /// The purpose of this function is to rollback a transaction or to drop query result in cases,
99    /// where `Transaction` was dropped without an explicit call to `commit` or `rollback`,
100    /// or where `QueryResult` was dropped without being consumed.
101    ///
102    /// The difference betwee this function and [`Conn::cleanup`] is that this function
103    /// won't rollback existing transaction. Another difference, is that this function
104    /// won't ignore non-fatal errors.
105    pub(crate) async fn clean_dirty(&mut self) -> Result<()> {
106        self.drop_result().await?;
107        if self.get_tx_status() == TxStatus::RequiresRollback {
108            self.rollback_transaction().await?;
109        }
110        Ok(())
111    }
112
113    /// Low level function that performs a text query.
114    pub(crate) async fn raw_query<'a, Q, L: TracingLevel>(&'a mut self, query: Q) -> Result<()>
115    where
116        Q: AsQuery + 'a,
117    {
118        self.routine(QueryRoutine::<'_, L>::new(query.as_query().as_ref()))
119            .await
120    }
121
122    /// Used for internal querying of connection settings,
123    /// bypassing instrumentation meant for user queries.
124    // This is a merge of `Queryable::query_first` and `Conn::query_iter`.
125    // TODO: find a cleaner way without duplicating code.
126    pub(crate) fn query_internal<'a, T, Q>(&'a mut self, query: Q) -> BoxFuture<'a, Option<T>>
127    where
128        Q: AsQuery + 'a,
129        T: FromRow + Send + 'static,
130    {
131        async move {
132            self.raw_query::<'_, _, LevelTrace>(query).await?;
133            Ok(QueryResult::<'_, '_, TextProtocol>::new(self)
134                .collect_and_drop::<T>()
135                .await?
136                .pop())
137        }
138        .boxed()
139    }
140}
141
142/// Methods of this trait are used to execute database queries.
143///
144/// `Conn` is a `Queryable` as well as `Transaction`.
145pub trait Queryable: Send {
146    /// Executes `COM_PING`.
147    fn ping(&mut self) -> BoxFuture<'_, ()>;
148
149    /// Performs the given query and returns the result.
150    fn query_iter<'a, Q>(
151        &'a mut self,
152        query: Q,
153    ) -> BoxFuture<'a, QueryResult<'a, 'static, TextProtocol>>
154    where
155        Q: AsQuery + 'a;
156
157    /// Prepares the given statement.
158    ///
159    /// Note, that `Statement` will exist only in the context of this queryable.
160    ///
161    /// Also note, that this call may close the least recently used statement
162    /// if statement cache is at its capacity (see. [`stmt_cache_size`][stmt_cache_size]).
163    ///
164    /// [stmt_cache_size]: crate::Opts::stmt_cache_size
165    fn prep<'a, Q>(&'a mut self, query: Q) -> BoxFuture<'a, Statement>
166    where
167        Q: AsQuery + 'a;
168
169    /// Closes the given statement.
170    ///
171    /// Usually there is no need to explicitly close statements
172    /// (see. [`stmt_cache_size`][stmt_cache_size]).
173    ///
174    /// [stmt_cache_size]: crate::Opts::stmt_cache_size
175    fn close(&mut self, stmt: Statement) -> BoxFuture<'_, ()>;
176
177    /// Executes the given statement with given params.
178    ///
179    /// It'll prepare `stmt`, if necessary.
180    fn exec_iter<'a: 's, 's, Q, P>(
181        &'a mut self,
182        stmt: Q,
183        params: P,
184    ) -> BoxFuture<'s, QueryResult<'a, 'static, BinaryProtocol>>
185    where
186        Q: StatementLike + 's,
187        P: Into<Params>;
188
189    /// Performs the given query and collects the first result set.
190    ///
191    /// ## Conversion
192    ///
193    /// This stream will convert each row into `T` using [`FromRow`] implementation.
194    /// If the row type is unknown please use the [`Row`] type for `T`
195    /// to make this conversion infallible.
196    fn query<'a, T, Q>(&'a mut self, query: Q) -> BoxFuture<'a, Vec<T>>
197    where
198        Q: AsQuery + 'a,
199        T: FromRow + Send + 'static,
200    {
201        async move { self.query_iter(query).await?.collect_and_drop::<T>().await }.boxed()
202    }
203
204    /// Performs the given query and returns the first row of the first result set.
205    ///
206    /// ## Conversion
207    ///
208    /// This stream will convert each row into `T` using [`FromRow`] implementation.
209    /// If the row type is unknown please use the [`Row`] type for `T`
210    /// to make this conversion infallible.
211    fn query_first<'a, T, Q>(&'a mut self, query: Q) -> BoxFuture<'a, Option<T>>
212    where
213        Q: AsQuery + 'a,
214        T: FromRow + Send + 'static,
215    {
216        async move {
217            let mut result = self.query_iter(query).await?;
218            let output = if result.is_empty() {
219                None
220            } else {
221                result.next().await?.map(crate::from_row)
222            };
223            result.drop_result().await?;
224            Ok(output)
225        }
226        .boxed()
227    }
228
229    /// Performs the given query and maps each row of the first result set.
230    ///
231    /// ## Conversion
232    ///
233    /// This stream will convert each row into `T` using [`FromRow`] implementation.
234    /// If the row type is unknown please use the [`Row`] type for `T`
235    /// to make this conversion infallible.
236    fn query_map<'a, T, F, Q, U>(&'a mut self, query: Q, mut f: F) -> BoxFuture<'a, Vec<U>>
237    where
238        Q: AsQuery + 'a,
239        T: FromRow + Send + 'static,
240        F: FnMut(T) -> U + Send + 'a,
241        U: Send,
242    {
243        async move {
244            self.query_fold(query, Vec::new(), |mut acc, row| {
245                acc.push(f(crate::from_row(row)));
246                acc
247            })
248            .await
249        }
250        .boxed()
251    }
252
253    /// Performs the given query and folds the first result set to a single value.
254    ///
255    /// ## Conversion
256    ///
257    /// This stream will convert each row into `T` using [`FromRow`] implementation.
258    /// If the row type is unknown please use the [`Row`] type for `T`
259    /// to make this conversion infallible.
260    fn query_fold<'a, T, F, Q, U>(&'a mut self, query: Q, init: U, mut f: F) -> BoxFuture<'a, U>
261    where
262        Q: AsQuery + 'a,
263        T: FromRow + Send + 'static,
264        F: FnMut(U, T) -> U + Send + 'a,
265        U: Send + 'a,
266    {
267        async move {
268            self.query_iter(query)
269                .await?
270                .reduce_and_drop(init, |acc, row| f(acc, crate::from_row(row)))
271                .await
272        }
273        .boxed()
274    }
275
276    /// Performs the given query and drops the query result.
277    fn query_drop<'a, Q>(&'a mut self, query: Q) -> BoxFuture<'a, ()>
278    where
279        Q: AsQuery + 'a,
280    {
281        async move { self.query_iter(query).await?.drop_result().await }.boxed()
282    }
283
284    /// Executes the given statement for each item in the given params iterator.
285    ///
286    /// It'll prepare `stmt` (once), if necessary.
287    ///
288    /// # Note
289    ///
290    /// It will use `COM_STMT_BULK_EXECUTE` for MariaDb >= v10.2 if
291    /// `MARIADB_CLIENT_STMT_BULK_OPERATIONS` is enabled by the server. Practically
292    /// this means that the function will error with code 1295 (`ER_UNSUPPORTED_PS`)
293    /// for _non-bulk execution safe_ operations, namely for all operations
294    /// except UPDATE, multi-UPDATE, INSERT, DELETE and REPLACE. Consider not
295    /// using exec batch for operations outside of this list.
296    fn exec_batch<'a: 'b, 'b, S, P, I>(&'a mut self, stmt: S, params_iter: I) -> BoxFuture<'b, ()>
297    where
298        S: StatementLike + 'b,
299        I: IntoIterator<Item = P> + Send + 'b,
300        I::IntoIter: Send,
301        P: Into<Params> + Send;
302
303    /// Executes the given statement and collects the first result set.
304    ///
305    /// It'll prepare `stmt`, if necessary.
306    ///
307    /// ## Conversion
308    ///
309    /// This stream will convert each row into `T` using [`FromRow`] implementation.
310    /// If the row type is unknown please use the [`Row`] type for `T`
311    /// to make this conversion infallible.
312    fn exec<'a: 'b, 'b, T, S, P>(&'a mut self, stmt: S, params: P) -> BoxFuture<'b, Vec<T>>
313    where
314        S: StatementLike + 'b,
315        P: Into<Params> + Send + 'b,
316        T: FromRow + Send + 'static,
317    {
318        async move {
319            self.exec_iter(stmt, params)
320                .await?
321                .collect_and_drop::<T>()
322                .await
323        }
324        .boxed()
325    }
326
327    /// Executes the given statement and returns the first row of the first result set.
328    ///
329    /// It'll prepare `stmt`, if necessary.
330    ///
331    /// ## Conversion
332    ///
333    /// This stream will convert each row into `T` using [`FromRow`] implementation.
334    /// If the row type is unknown please use the [`Row`] type for `T`
335    /// to make this conversion infallible.
336    fn exec_first<'a: 'b, 'b, T, S, P>(&'a mut self, stmt: S, params: P) -> BoxFuture<'b, Option<T>>
337    where
338        S: StatementLike + 'b,
339        P: Into<Params> + Send + 'b,
340        T: FromRow + Send + 'static,
341    {
342        async move {
343            let mut result = self.exec_iter(stmt, params).await?;
344            let row = if result.is_empty() {
345                None
346            } else {
347                result.next().await?
348            };
349            result.drop_result().await?;
350            Ok(row.map(crate::from_row))
351        }
352        .boxed()
353    }
354
355    /// Executes the given stmt and maps each row of the first result set.
356    ///
357    /// It'll prepare `stmt`, if necessary.
358    ///
359    /// ## Conversion
360    ///
361    /// This stream will convert each row into `T` using [`FromRow`] implementation.
362    /// If the row type is unknown please use the [`Row`] type for `T`
363    /// to make this conversion infallible.
364    fn exec_map<'a: 'b, 'b, T, S, P, U, F>(
365        &'a mut self,
366        stmt: S,
367        params: P,
368        mut f: F,
369    ) -> BoxFuture<'b, Vec<U>>
370    where
371        S: StatementLike + 'b,
372        P: Into<Params> + Send + 'b,
373        T: FromRow + Send + 'static,
374        F: FnMut(T) -> U + Send + 'a,
375        U: Send + 'a,
376    {
377        async move {
378            self.exec_fold(stmt, params, Vec::new(), |mut acc, row| {
379                acc.push(f(crate::from_row(row)));
380                acc
381            })
382            .await
383        }
384        .boxed()
385    }
386
387    /// Executes the given stmt and folds the first result set to a signel value.
388    ///
389    /// It'll prepare `stmt`, if necessary.
390    ///
391    /// ## Conversion
392    ///
393    /// This stream will convert each row into `T` using [`FromRow`] implementation.
394    /// If the row type is unknown please use the [`Row`] type for `T`
395    /// to make this conversion infallible.
396    fn exec_fold<'a: 'b, 'b, T, S, P, U, F>(
397        &'a mut self,
398        stmt: S,
399        params: P,
400        init: U,
401        mut f: F,
402    ) -> BoxFuture<'b, U>
403    where
404        S: StatementLike + 'b,
405        P: Into<Params> + Send + 'b,
406        T: FromRow + Send + 'static,
407        F: FnMut(U, T) -> U + Send + 'a,
408        U: Send + 'a,
409    {
410        async move {
411            self.exec_iter(stmt, params)
412                .await?
413                .reduce_and_drop(init, |acc, row| f(acc, crate::from_row(row)))
414                .await
415        }
416        .boxed()
417    }
418
419    /// Executes the given statement and drops the result.
420    fn exec_drop<'a: 'b, 'b, S, P>(&'a mut self, stmt: S, params: P) -> BoxFuture<'b, ()>
421    where
422        S: StatementLike + 'b,
423        P: Into<Params> + Send + 'b,
424    {
425        async move { self.exec_iter(stmt, params).await?.drop_result().await }.boxed()
426    }
427
428    /// Returns a stream over the first result set.
429    ///
430    /// Please see [`QueryResult::stream_and_drop`][stream_and_drop].
431    ///
432    /// [stream_and_drop]: crate::QueryResult::stream_and_drop
433    fn query_stream<'a, T, Q>(
434        &'a mut self,
435        query: Q,
436    ) -> BoxFuture<'a, ResultSetStream<'a, 'a, 'static, T, TextProtocol>>
437    where
438        T: Unpin + FromRow + Send + 'static,
439        Q: AsQuery + 'a,
440    {
441        async move {
442            self.query_iter(query)
443                .await?
444                .stream_and_drop()
445                .await
446                .transpose()
447                .expect("At least one result set is expected")
448        }
449        .boxed()
450    }
451
452    /// Returns a stream over the first result set.
453    ///
454    /// Please see [`QueryResult::stream_and_drop`][stream_and_drop].
455    ///
456    /// [stream_and_drop]: crate::QueryResult::stream_and_drop
457    fn exec_stream<'a: 's, 's, T, Q, P>(
458        &'a mut self,
459        stmt: Q,
460        params: P,
461    ) -> BoxFuture<'s, ResultSetStream<'a, 'a, 'static, T, BinaryProtocol>>
462    where
463        T: Unpin + FromRow + Send + 'static,
464        Q: StatementLike + 's,
465        P: Into<Params> + Send + 's,
466    {
467        async move {
468            self.exec_iter(stmt, params)
469                .await?
470                .stream_and_drop()
471                .await
472                .transpose()
473                .expect("At least one result set is expected")
474        }
475        .boxed()
476    }
477}
478
479impl Queryable for Conn {
480    fn ping(&mut self) -> BoxFuture<'_, ()> {
481        async move {
482            self.routine(PingRoutine).await?;
483            Ok(())
484        }
485        .boxed()
486    }
487
488    fn query_iter<'a, Q>(
489        &'a mut self,
490        query: Q,
491    ) -> BoxFuture<'a, QueryResult<'a, 'static, TextProtocol>>
492    where
493        Q: AsQuery + 'a,
494    {
495        async move {
496            self.raw_query::<'_, _, LevelInfo>(query).await?;
497            Ok(QueryResult::new(self))
498        }
499        .boxed()
500    }
501
502    fn prep<'a, Q>(&'a mut self, query: Q) -> BoxFuture<'a, Statement>
503    where
504        Q: AsQuery + 'a,
505    {
506        async move { self.get_statement(query.as_query()).await }.boxed()
507    }
508
509    fn close(&mut self, stmt: Statement) -> BoxFuture<'_, ()> {
510        async move {
511            self.stmt_cache_mut().remove(stmt.id());
512            self.close_statement(stmt.id()).await
513        }
514        .boxed()
515    }
516
517    fn exec_iter<'a: 's, 's, Q, P>(
518        &'a mut self,
519        stmt: Q,
520        params: P,
521    ) -> BoxFuture<'s, QueryResult<'a, 'static, BinaryProtocol>>
522    where
523        Q: StatementLike + 's,
524        P: Into<Params>,
525    {
526        let params = params.into();
527        async move {
528            let statement = self.get_statement(stmt).await?;
529            self.execute_statement(&statement, params).await?;
530            Ok(QueryResult::new(self))
531        }
532        .boxed()
533    }
534
535    fn exec_batch<'a: 'b, 'b, S, P, I>(&'a mut self, stmt: S, params_iter: I) -> BoxFuture<'b, ()>
536    where
537        S: StatementLike + 'b,
538        I: IntoIterator<Item = P> + Send + 'b,
539        I::IntoIter: Send,
540        P: Into<Params> + Send,
541    {
542        async move {
543            let statement = self.get_statement(stmt).await?;
544            if self
545                .has_mariadb_capabilities(MariadbCapabilities::MARIADB_CLIENT_STMT_BULK_OPERATIONS)
546                && statement.num_params() > 0
547            {
548                self.execute_bulk(&statement, params_iter).await?;
549            } else {
550                for params in params_iter {
551                    self.execute_statement(&statement, params).await?;
552                    QueryResult::<BinaryProtocol>::new(&mut *self)
553                        .drop_result()
554                        .await?;
555                }
556            }
557            Ok(())
558        }
559        .boxed()
560    }
561}
562
563impl Queryable for Transaction<'_> {
564    fn ping(&mut self) -> BoxFuture<'_, ()> {
565        self.0.as_mut().ping()
566    }
567
568    fn query_iter<'a, Q>(
569        &'a mut self,
570        query: Q,
571    ) -> BoxFuture<'a, QueryResult<'a, 'static, TextProtocol>>
572    where
573        Q: AsQuery + 'a,
574    {
575        self.0.as_mut().query_iter(query)
576    }
577
578    fn prep<'a, Q>(&'a mut self, query: Q) -> BoxFuture<'a, Statement>
579    where
580        Q: AsQuery + 'a,
581    {
582        self.0.as_mut().prep(query)
583    }
584
585    fn close(&mut self, stmt: Statement) -> BoxFuture<'_, ()> {
586        self.0.as_mut().close(stmt)
587    }
588
589    fn exec_iter<'a: 's, 's, Q, P>(
590        &'a mut self,
591        stmt: Q,
592        params: P,
593    ) -> BoxFuture<'s, QueryResult<'a, 'static, BinaryProtocol>>
594    where
595        Q: StatementLike + 's,
596        P: Into<Params>,
597    {
598        self.0.as_mut().exec_iter(stmt, params)
599    }
600
601    fn exec_batch<'a: 'b, 'b, S, P, I>(&'a mut self, stmt: S, params_iter: I) -> BoxFuture<'b, ()>
602    where
603        S: StatementLike + 'b,
604        I: IntoIterator<Item = P> + Send + 'b,
605        I::IntoIter: Send,
606        P: Into<Params> + Send,
607    {
608        self.0.as_mut().exec_batch(stmt, params_iter)
609    }
610}
611
612impl<'c, 't: 'c> Queryable for Connection<'c, 't> {
613    #[inline]
614    fn ping(&mut self) -> BoxFuture<'_, ()> {
615        self.as_mut().ping()
616    }
617
618    #[inline]
619    fn query_iter<'a, Q>(
620        &'a mut self,
621        query: Q,
622    ) -> BoxFuture<'a, QueryResult<'a, 'static, TextProtocol>>
623    where
624        Q: AsQuery + 'a,
625    {
626        self.as_mut().query_iter(query)
627    }
628
629    fn prep<'a, Q>(&'a mut self, query: Q) -> BoxFuture<'a, Statement>
630    where
631        Q: AsQuery + 'a,
632    {
633        self.as_mut().prep(query)
634    }
635
636    fn close(&mut self, stmt: Statement) -> BoxFuture<'_, ()> {
637        self.as_mut().close(stmt)
638    }
639
640    fn exec_iter<'a: 's, 's, Q, P>(
641        &'a mut self,
642        stmt: Q,
643        params: P,
644    ) -> BoxFuture<'s, QueryResult<'a, 'static, BinaryProtocol>>
645    where
646        Q: StatementLike + 's,
647        P: Into<Params>,
648    {
649        self.as_mut().exec_iter(stmt, params)
650    }
651
652    fn exec_batch<'a: 'b, 'b, S, P, I>(&'a mut self, stmt: S, params_iter: I) -> BoxFuture<'b, ()>
653    where
654        S: StatementLike + 'b,
655        I: IntoIterator<Item = P> + Send + 'b,
656        I::IntoIter: Send,
657        P: Into<Params> + Send,
658    {
659        self.as_mut().exec_batch(stmt, params_iter)
660    }
661}
662
663#[cfg(test)]
664mod tests {
665    use crate::{prelude::*, test_misc::get_opts, Conn};
666
667    #[tokio::test]
668    async fn should_prep() {
669        const NAMED: &str = "SELECT :foo, :bar, :foo";
670        const POSITIONAL: &str = "SELECT ?, ?, ?";
671
672        let mut conn = Conn::new(get_opts()).await.unwrap();
673
674        let stmt_named = conn.prep(NAMED).await.unwrap();
675        let stmt_positional = conn.prep(POSITIONAL).await.unwrap();
676
677        let result_stmt_named: Option<(String, u8, String)> = conn
678            .exec_first(&stmt_named, params! { "foo" => "bar", "bar" => 42 })
679            .await
680            .unwrap();
681        let result_str_named: Option<(String, u8, String)> = conn
682            .exec_first(NAMED, params! { "foo" => "bar", "bar" => 42 })
683            .await
684            .unwrap();
685
686        let result_stmt_positional: Option<(String, u8, String)> = conn
687            .exec_first(&stmt_positional, ("bar", 42, "bar"))
688            .await
689            .unwrap();
690        let result_str_positional: Option<(String, u8, String)> = conn
691            .exec_first(NAMED, params! { "foo" => "bar", "bar" => 42 })
692            .await
693            .unwrap();
694
695        assert_eq!(
696            Some(("bar".to_owned(), 42_u8, "bar".to_owned())),
697            result_stmt_named
698        );
699        assert_eq!(result_stmt_named, result_str_named);
700        assert_eq!(result_str_named, result_stmt_positional);
701        assert_eq!(result_stmt_positional, result_str_positional);
702
703        conn.disconnect().await.unwrap();
704    }
705}