Skip to main content

tokio_postgres/
client.rs

1use crate::codec::{BackendMessages, FrontendMessage};
2use crate::config::{SslMode, SslNegotiation};
3use crate::connection::{Request, RequestMessages};
4use crate::copy_out::CopyOutStream;
5#[cfg(feature = "runtime")]
6use crate::keepalive::KeepaliveConfig;
7use crate::query::RowStream;
8use crate::simple_query::SimpleQueryStream;
9#[cfg(feature = "runtime")]
10use crate::tls::MakeTlsConnect;
11use crate::tls::TlsConnect;
12use crate::types::{Oid, ToSql, Type};
13#[cfg(feature = "runtime")]
14use crate::Socket;
15use crate::{
16    copy_in, copy_out, prepare, query, simple_query, slice_iter, CancelToken, CopyInSink, Error,
17    Row, SimpleQueryMessage, Statement, ToStatement, Transaction, TransactionBuilder,
18};
19use bytes::{Buf, BytesMut};
20use fallible_iterator::FallibleIterator;
21use futures_channel::mpsc;
22use futures_util::{future, pin_mut, ready, StreamExt, TryStreamExt};
23use parking_lot::Mutex;
24use postgres_protocol::message::backend::Message;
25use postgres_protocol::message::frontend;
26use postgres_types::BorrowToSql;
27use std::collections::HashMap;
28use std::fmt;
29#[cfg(feature = "runtime")]
30use std::net::IpAddr;
31#[cfg(feature = "runtime")]
32use std::path::PathBuf;
33use std::sync::Arc;
34use std::task::{Context, Poll};
35#[cfg(feature = "runtime")]
36use std::time::Duration;
37use tokio::io::{AsyncRead, AsyncWrite};
38
39pub struct Responses {
40    receiver: mpsc::Receiver<BackendMessages>,
41    cur: BackendMessages,
42}
43
44impl Responses {
45    pub fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Result<Message, Error>> {
46        loop {
47            match self.cur.next().map_err(Error::parse)? {
48                Some(Message::ErrorResponse(body)) => return Poll::Ready(Err(Error::db(body))),
49                Some(message) => return Poll::Ready(Ok(message)),
50                None => {}
51            }
52
53            match ready!(self.receiver.poll_next_unpin(cx)) {
54                Some(messages) => self.cur = messages,
55                None => return Poll::Ready(Err(Error::closed())),
56            }
57        }
58    }
59
60    pub async fn next(&mut self) -> Result<Message, Error> {
61        future::poll_fn(|cx| self.poll_next(cx)).await
62    }
63}
64
65/// A cache of type info and prepared statements for fetching type info
66/// (corresponding to the queries in the [prepare](prepare) module).
67#[derive(Default)]
68struct CachedTypeInfo {
69    /// A statement for basic information for a type from its
70    /// OID. Corresponds to [TYPEINFO_QUERY](prepare::TYPEINFO_QUERY) (or its
71    /// fallback).
72    typeinfo: Option<Statement>,
73    /// A statement for getting information for a composite type from its OID.
74    /// Corresponds to [TYPEINFO_QUERY](prepare::TYPEINFO_COMPOSITE_QUERY).
75    typeinfo_composite: Option<Statement>,
76    /// A statement for getting information for a composite type from its OID.
77    /// Corresponds to [TYPEINFO_QUERY](prepare::TYPEINFO_COMPOSITE_QUERY) (or
78    /// its fallback).
79    typeinfo_enum: Option<Statement>,
80
81    /// Cache of types already looked up.
82    types: HashMap<Oid, Type>,
83}
84
85pub struct InnerClient {
86    sender: mpsc::UnboundedSender<Request>,
87    cached_typeinfo: Mutex<CachedTypeInfo>,
88
89    /// A buffer to use when writing out postgres commands.
90    buffer: Mutex<BytesMut>,
91}
92
93impl InnerClient {
94    pub fn send(&self, messages: RequestMessages) -> Result<Responses, Error> {
95        let (sender, receiver) = mpsc::channel(1);
96        let request = Request { messages, sender };
97        self.sender
98            .unbounded_send(request)
99            .map_err(|_| Error::closed())?;
100
101        Ok(Responses {
102            receiver,
103            cur: BackendMessages::empty(),
104        })
105    }
106
107    pub fn typeinfo(&self) -> Option<Statement> {
108        self.cached_typeinfo.lock().typeinfo.clone()
109    }
110
111    pub fn set_typeinfo(&self, statement: &Statement) {
112        self.cached_typeinfo.lock().typeinfo = Some(statement.clone());
113    }
114
115    pub fn typeinfo_composite(&self) -> Option<Statement> {
116        self.cached_typeinfo.lock().typeinfo_composite.clone()
117    }
118
119    pub fn set_typeinfo_composite(&self, statement: &Statement) {
120        self.cached_typeinfo.lock().typeinfo_composite = Some(statement.clone());
121    }
122
123    pub fn typeinfo_enum(&self) -> Option<Statement> {
124        self.cached_typeinfo.lock().typeinfo_enum.clone()
125    }
126
127    pub fn set_typeinfo_enum(&self, statement: &Statement) {
128        self.cached_typeinfo.lock().typeinfo_enum = Some(statement.clone());
129    }
130
131    pub fn type_(&self, oid: Oid) -> Option<Type> {
132        self.cached_typeinfo.lock().types.get(&oid).cloned()
133    }
134
135    pub fn set_type(&self, oid: Oid, type_: &Type) {
136        self.cached_typeinfo.lock().types.insert(oid, type_.clone());
137    }
138
139    pub fn clear_type_cache(&self) {
140        self.cached_typeinfo.lock().types.clear();
141    }
142
143    /// Call the given function with a buffer to be used when writing out
144    /// postgres commands.
145    pub fn with_buf<F, R>(&self, f: F) -> R
146    where
147        F: FnOnce(&mut BytesMut) -> R,
148    {
149        let mut buffer = self.buffer.lock();
150        let r = f(&mut buffer);
151        buffer.clear();
152        r
153    }
154}
155
156#[cfg(feature = "runtime")]
157#[derive(Clone)]
158pub(crate) struct SocketConfig {
159    pub addr: Addr,
160    pub hostname: Option<String>,
161    pub port: u16,
162    pub connect_timeout: Option<Duration>,
163    pub tcp_user_timeout: Option<Duration>,
164    pub keepalive: Option<KeepaliveConfig>,
165}
166
167#[cfg(feature = "runtime")]
168#[derive(Clone)]
169pub(crate) enum Addr {
170    Tcp(IpAddr),
171    #[cfg(unix)]
172    Unix(PathBuf),
173}
174
175/// An asynchronous PostgreSQL client.
176///
177/// The client is one half of what is returned when a connection is established. Users interact with the database
178/// through this client object.
179pub struct Client {
180    inner: Arc<InnerClient>,
181    #[cfg(feature = "runtime")]
182    socket_config: Option<SocketConfig>,
183    ssl_mode: SslMode,
184    ssl_negotiation: SslNegotiation,
185    process_id: i32,
186    secret_key: i32,
187}
188
189impl Client {
190    pub(crate) fn new(
191        sender: mpsc::UnboundedSender<Request>,
192        ssl_mode: SslMode,
193        ssl_negotiation: SslNegotiation,
194        process_id: i32,
195        secret_key: i32,
196    ) -> Client {
197        Client {
198            inner: Arc::new(InnerClient {
199                sender,
200                cached_typeinfo: Default::default(),
201                buffer: Default::default(),
202            }),
203            #[cfg(feature = "runtime")]
204            socket_config: None,
205            ssl_mode,
206            ssl_negotiation,
207            process_id,
208            secret_key,
209        }
210    }
211
212    pub(crate) fn inner(&self) -> &Arc<InnerClient> {
213        &self.inner
214    }
215
216    #[cfg(feature = "runtime")]
217    pub(crate) fn set_socket_config(&mut self, socket_config: SocketConfig) {
218        self.socket_config = Some(socket_config);
219    }
220
221    /// Creates a new prepared statement.
222    ///
223    /// Prepared statements can be executed repeatedly, and may contain query parameters (indicated by `$1`, `$2`, etc),
224    /// which are set when executed. Prepared statements can only be used with the connection that created them.
225    pub async fn prepare(&self, query: &str) -> Result<Statement, Error> {
226        self.prepare_typed(query, &[]).await
227    }
228
229    /// Like `prepare`, but allows the types of query parameters to be explicitly specified.
230    ///
231    /// The list of types may be smaller than the number of parameters - the types of the remaining parameters will be
232    /// inferred. For example, `client.prepare_typed(query, &[])` is equivalent to `client.prepare(query)`.
233    pub async fn prepare_typed(
234        &self,
235        query: &str,
236        parameter_types: &[Type],
237    ) -> Result<Statement, Error> {
238        prepare::prepare(&self.inner, query, parameter_types).await
239    }
240
241    /// Executes a statement, returning a vector of the resulting rows.
242    ///
243    /// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
244    /// provided, 1-indexed.
245    ///
246    /// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
247    /// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
248    /// with the `prepare` method.
249    pub async fn query<T>(
250        &self,
251        statement: &T,
252        params: &[&(dyn ToSql + Sync)],
253    ) -> Result<Vec<Row>, Error>
254    where
255        T: ?Sized + ToStatement,
256    {
257        self.query_raw(statement, slice_iter(params))
258            .await?
259            .try_collect()
260            .await
261    }
262
263    /// Executes a statement which returns a single row, returning it.
264    ///
265    /// Returns an error if the query does not return exactly one row.
266    ///
267    /// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
268    /// provided, 1-indexed.
269    ///
270    /// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
271    /// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
272    /// with the `prepare` method.
273    pub async fn query_one<T>(
274        &self,
275        statement: &T,
276        params: &[&(dyn ToSql + Sync)],
277    ) -> Result<Row, Error>
278    where
279        T: ?Sized + ToStatement,
280    {
281        self.query_opt(statement, params)
282            .await
283            .and_then(|res| res.ok_or_else(Error::row_count))
284    }
285
286    /// Executes a statements which returns zero or one rows, returning it.
287    ///
288    /// Returns an error if the query returns more than one row.
289    ///
290    /// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
291    /// provided, 1-indexed.
292    ///
293    /// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
294    /// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
295    /// with the `prepare` method.
296    pub async fn query_opt<T>(
297        &self,
298        statement: &T,
299        params: &[&(dyn ToSql + Sync)],
300    ) -> Result<Option<Row>, Error>
301    where
302        T: ?Sized + ToStatement,
303    {
304        let stream = self.query_raw(statement, slice_iter(params)).await?;
305        pin_mut!(stream);
306
307        let mut first = None;
308
309        // Originally this was two calls to `try_next().await?`,
310        // once for the first element, and second to error if more than one.
311        //
312        // However, this new form with only one .await in a loop generates
313        // slightly smaller codegen/stack usage for the resulting future.
314        while let Some(row) = stream.try_next().await? {
315            if first.is_some() {
316                return Err(Error::row_count());
317            }
318
319            first = Some(row);
320        }
321
322        Ok(first)
323    }
324
325    /// The maximally flexible version of [`query`].
326    ///
327    /// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
328    /// provided, 1-indexed.
329    ///
330    /// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
331    /// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
332    /// with the `prepare` method.
333    ///
334    /// [`query`]: #method.query
335    ///
336    /// # Examples
337    ///
338    /// ```no_run
339    /// # async fn async_main(client: &tokio_postgres::Client) -> Result<(), tokio_postgres::Error> {
340    /// use futures_util::{pin_mut, TryStreamExt};
341    ///
342    /// let params: Vec<String> = vec![
343    ///     "first param".into(),
344    ///     "second param".into(),
345    /// ];
346    /// let mut it = client.query_raw(
347    ///     "SELECT foo FROM bar WHERE biz = $1 AND baz = $2",
348    ///     params,
349    /// ).await?;
350    ///
351    /// pin_mut!(it);
352    /// while let Some(row) = it.try_next().await? {
353    ///     let foo: i32 = row.get("foo");
354    ///     println!("foo: {}", foo);
355    /// }
356    /// # Ok(())
357    /// # }
358    /// ```
359    pub async fn query_raw<T, P, I>(&self, statement: &T, params: I) -> Result<RowStream, Error>
360    where
361        T: ?Sized + ToStatement,
362        P: BorrowToSql,
363        I: IntoIterator<Item = P>,
364        I::IntoIter: ExactSizeIterator,
365    {
366        let statement = statement.__convert().into_statement(self).await?;
367        query::query(&self.inner, statement, params).await
368    }
369
370    /// Like `query_raw`, but allows specifying the result format (text or binary).
371    ///
372    /// With `ResultFormat::Text`, PostgreSQL returns column values as UTF-8 strings —
373    /// the same format the pg Node.js library receives. This is useful for building
374    /// driver adapters for ORMs that apply their own type parsing.
375    pub async fn query_raw_with_format<T, P, I>(
376        &self,
377        statement: &T,
378        params: I,
379        result_format: crate::ResultFormat,
380    ) -> Result<RowStream, Error>
381    where
382        T: ?Sized + ToStatement,
383        P: BorrowToSql,
384        I: IntoIterator<Item = P>,
385        I::IntoIter: ExactSizeIterator,
386    {
387        let statement = statement.__convert().into_statement(self).await?;
388        query::query_with_format(&self.inner, statement, params, result_format).await
389    }
390
391    /// Like `query`, but requires the types of query parameters to be explicitly specified.
392    ///
393    /// Compared to `query`, this method allows performing queries without three round trips (for
394    /// prepare, execute, and close) by requiring the caller to specify parameter values along with
395    /// their Postgres type. Thus, this is suitable in environments where prepared statements aren't
396    /// supported (such as Cloudflare Workers with Hyperdrive).
397    ///
398    /// A statement may contain parameters, specified by `$n`, where `n` is the index of the
399    /// parameter of the list provided, 1-indexed.
400    pub async fn query_typed(
401        &self,
402        query: &str,
403        params: &[(&(dyn ToSql + Sync), Type)],
404    ) -> Result<Vec<Row>, Error> {
405        self.query_typed_raw(query, params.iter().map(|(v, t)| (*v, t.clone())))
406            .await?
407            .try_collect()
408            .await
409    }
410
411    /// The maximally flexible version of [`query_typed`].
412    ///
413    /// Compared to `query`, this method allows performing queries without three round trips (for
414    /// prepare, execute, and close) by requiring the caller to specify parameter values along with
415    /// their Postgres type. Thus, this is suitable in environments where prepared statements aren't
416    /// supported (such as Cloudflare Workers with Hyperdrive).
417    ///
418    /// A statement may contain parameters, specified by `$n`, where `n` is the index of the
419    /// parameter of the list provided, 1-indexed.
420    ///
421    /// [`query_typed`]: #method.query_typed
422    ///
423    /// # Examples
424    ///
425    /// ```no_run
426    /// # async fn async_main(client: &tokio_postgres::Client) -> Result<(), tokio_postgres::Error> {
427    /// use futures_util::{pin_mut, TryStreamExt};
428    /// use tokio_postgres::types::Type;
429    ///
430    /// let params: Vec<(String, Type)> = vec![
431    ///     ("first param".into(), Type::TEXT),
432    ///     ("second param".into(), Type::TEXT),
433    /// ];
434    /// let mut it = client.query_typed_raw(
435    ///     "SELECT foo FROM bar WHERE biz = $1 AND baz = $2",
436    ///     params,
437    /// ).await?;
438    ///
439    /// pin_mut!(it);
440    /// while let Some(row) = it.try_next().await? {
441    ///     let foo: i32 = row.get("foo");
442    ///     println!("foo: {}", foo);
443    /// }
444    /// # Ok(())
445    /// # }
446    /// ```
447    pub async fn query_typed_raw<P, I>(&self, query: &str, params: I) -> Result<RowStream, Error>
448    where
449        P: BorrowToSql,
450        I: IntoIterator<Item = (P, Type)>,
451    {
452        query::query_typed(&self.inner, query, params).await
453    }
454
455    /// Executes a statement, returning the number of rows modified.
456    ///
457    /// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
458    /// provided, 1-indexed.
459    ///
460    /// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
461    /// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
462    /// with the `prepare` method.
463    ///
464    /// If the statement does not modify any rows (e.g. `SELECT`), 0 is returned.
465    pub async fn execute<T>(
466        &self,
467        statement: &T,
468        params: &[&(dyn ToSql + Sync)],
469    ) -> Result<u64, Error>
470    where
471        T: ?Sized + ToStatement,
472    {
473        self.execute_raw(statement, slice_iter(params)).await
474    }
475
476    /// The maximally flexible version of [`execute`].
477    ///
478    /// A statement may contain parameters, specified by `$n`, where `n` is the index of the parameter of the list
479    /// provided, 1-indexed.
480    ///
481    /// The `statement` argument can either be a `Statement`, or a raw query string. If the same statement will be
482    /// repeatedly executed (perhaps with different query parameters), consider preparing the statement up front
483    /// with the `prepare` method.
484    ///
485    /// [`execute`]: #method.execute
486    pub async fn execute_raw<T, P, I>(&self, statement: &T, params: I) -> Result<u64, Error>
487    where
488        T: ?Sized + ToStatement,
489        P: BorrowToSql,
490        I: IntoIterator<Item = P>,
491        I::IntoIter: ExactSizeIterator,
492    {
493        let statement = statement.__convert().into_statement(self).await?;
494        query::execute(self.inner(), statement, params).await
495    }
496
497    /// Executes a `COPY FROM STDIN` statement, returning a sink used to write the copy data.
498    ///
499    /// PostgreSQL does not support parameters in `COPY` statements, so this method does not take any. The copy *must*
500    /// be explicitly completed via the `Sink::close` or `finish` methods. If it is not, the copy will be aborted.
501    pub async fn copy_in<T, U>(&self, statement: &T) -> Result<CopyInSink<U>, Error>
502    where
503        T: ?Sized + ToStatement,
504        U: Buf + 'static + Send,
505    {
506        let statement = statement.__convert().into_statement(self).await?;
507        copy_in::copy_in(self.inner(), statement).await
508    }
509
510    /// Executes a `COPY TO STDOUT` statement, returning a stream of the resulting data.
511    ///
512    /// PostgreSQL does not support parameters in `COPY` statements, so this method does not take any.
513    pub async fn copy_out<T>(&self, statement: &T) -> Result<CopyOutStream, Error>
514    where
515        T: ?Sized + ToStatement,
516    {
517        let statement = statement.__convert().into_statement(self).await?;
518        copy_out::copy_out(self.inner(), statement).await
519    }
520
521    /// Executes a sequence of SQL statements using the simple query protocol, returning the resulting rows.
522    ///
523    /// Statements should be separated by semicolons. If an error occurs, execution of the sequence will stop at that
524    /// point. The simple query protocol returns the values in rows as strings rather than in their binary encodings,
525    /// so the associated row type doesn't work with the `FromSql` trait. Rather than simply returning a list of the
526    /// rows, this method returns a list of an enum which indicates either the completion of one of the commands,
527    /// or a row of data. This preserves the framing between the separate statements in the request.
528    ///
529    /// # Warning
530    ///
531    /// Prepared statements should be use for any query which contains user-specified data, as they provided the
532    /// functionality to safely embed that data in the request. Do not form statements via string concatenation and pass
533    /// them to this method!
534    pub async fn simple_query(&self, query: &str) -> Result<Vec<SimpleQueryMessage>, Error> {
535        self.simple_query_raw(query).await?.try_collect().await
536    }
537
538    pub(crate) async fn simple_query_raw(&self, query: &str) -> Result<SimpleQueryStream, Error> {
539        simple_query::simple_query(self.inner(), query).await
540    }
541
542    /// Executes a sequence of SQL statements using the simple query protocol.
543    ///
544    /// Statements should be separated by semicolons. If an error occurs, execution of the sequence will stop at that
545    /// point. This is intended for use when, for example, initializing a database schema.
546    ///
547    /// # Warning
548    ///
549    /// Prepared statements should be use for any query which contains user-specified data, as they provided the
550    /// functionality to safely embed that data in the request. Do not form statements via string concatenation and pass
551    /// them to this method!
552    pub async fn batch_execute(&self, query: &str) -> Result<(), Error> {
553        simple_query::batch_execute(self.inner(), query).await
554    }
555
556    /// Begins a new database transaction.
557    ///
558    /// The transaction will roll back by default - use the `commit` method to commit it.
559    pub async fn transaction(&mut self) -> Result<Transaction<'_>, Error> {
560        self.build_transaction().start().await
561    }
562
563    /// Returns a builder for a transaction with custom settings.
564    ///
565    /// Unlike the `transaction` method, the builder can be used to control the transaction's isolation level and other
566    /// attributes.
567    pub fn build_transaction(&mut self) -> TransactionBuilder<'_> {
568        TransactionBuilder::new(self)
569    }
570
571    /// Constructs a cancellation token that can later be used to request cancellation of a query running on the
572    /// connection associated with this client.
573    pub fn cancel_token(&self) -> CancelToken {
574        CancelToken {
575            #[cfg(feature = "runtime")]
576            socket_config: self.socket_config.clone(),
577            ssl_mode: self.ssl_mode,
578            ssl_negotiation: self.ssl_negotiation,
579            process_id: self.process_id,
580            secret_key: self.secret_key,
581        }
582    }
583
584    /// Attempts to cancel an in-progress query.
585    ///
586    /// The server provides no information about whether a cancellation attempt was successful or not. An error will
587    /// only be returned if the client was unable to connect to the database.
588    ///
589    /// Requires the `runtime` Cargo feature (enabled by default).
590    #[cfg(feature = "runtime")]
591    #[deprecated(since = "0.6.0", note = "use Client::cancel_token() instead")]
592    pub async fn cancel_query<T>(&self, tls: T) -> Result<(), Error>
593    where
594        T: MakeTlsConnect<Socket>,
595    {
596        self.cancel_token().cancel_query(tls).await
597    }
598
599    /// Like `cancel_query`, but uses a stream which is already connected to the server rather than opening a new
600    /// connection itself.
601    #[deprecated(since = "0.6.0", note = "use Client::cancel_token() instead")]
602    pub async fn cancel_query_raw<S, T>(&self, stream: S, tls: T) -> Result<(), Error>
603    where
604        S: AsyncRead + AsyncWrite + Unpin,
605        T: TlsConnect<S>,
606    {
607        self.cancel_token().cancel_query_raw(stream, tls).await
608    }
609
610    /// Clears the client's type information cache.
611    ///
612    /// When user-defined types are used in a query, the client loads their definitions from the database and caches
613    /// them for the lifetime of the client. If those definitions are changed in the database, this method can be used
614    /// to flush the local cache and allow the new, updated definitions to be loaded.
615    pub fn clear_type_cache(&self) {
616        self.inner().clear_type_cache();
617    }
618
619    /// Determines if the connection to the server has already closed.
620    ///
621    /// In that case, all future queries will fail.
622    pub fn is_closed(&self) -> bool {
623        self.inner.sender.is_closed()
624    }
625
626    #[doc(hidden)]
627    pub fn __private_api_close(&mut self) {
628        self.inner.sender.close_channel()
629    }
630
631    #[doc(hidden)]
632    pub fn __private_api_rollback(&self, name: Option<&str>) {
633        let buf = self.inner().with_buf(|buf| {
634            if let Some(name) = name {
635                frontend::query(&format!("ROLLBACK TO {}", name), buf).unwrap();
636            } else {
637                frontend::query("ROLLBACK", buf).unwrap();
638            }
639            buf.split().freeze()
640        });
641        let _ = self
642            .inner()
643            .send(RequestMessages::Single(FrontendMessage::Raw(buf)));
644    }
645}
646
647impl fmt::Debug for Client {
648    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649        f.debug_struct("Client").finish()
650    }
651}