mysql_async/lib.rs
1// Copyright (c) 2016 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
9//! Tokio based asynchronous MySql client library for The Rust Programming Language.
10//!
11//! # Installation
12//!
13//! The library is hosted on [crates.io](https://crates.io/crates/mysql_async/).
14//!
15//! ```toml
16//! [dependencies]
17//! mysql_async = "<desired version>"
18//! ```
19//!
20//! # Crate Features
21//!
22//! Default feature set is wide – it includes all default [`mysql_common` features][myslqcommonfeatures]
23//! as well as `native-tls`-based TLS support.
24//!
25//! ## List Of Features
26//!
27//! * `minimal` – enables only necessary features (at the moment the only necessary feature
28//! is `flate2` backend). Enables:
29//!
30//! - `flate2/zlib"
31//!
32//! **Example:**
33//!
34//! ```toml
35//! [dependencies]
36//! mysql_async = { version = "*", default-features = false, features = ["minimal"]}
37//! ```
38//!
39//! **Note:* it is possible to use another `flate2` backend by directly choosing it:
40//!
41//! ```toml
42//! [dependencies]
43//! mysql_async = { version = "*", default-features = false }
44//! flate2 = { version = "*", default-features = false, features = ["rust_backend"] }
45//! ```
46//!
47//! * `default` – enables the following set of crate's and dependencies' features:
48//!
49//! - `native-tls-tls`
50//! - `flate2/zlib"
51//! - `mysql_common/bigdecimal03`
52//! - `mysql_common/rust_decimal`
53//! - `mysql_common/time03`
54//! - `mysql_common/uuid`
55//! - `mysql_common/frunk`
56//! - `binlog`
57//!
58//! * `default-rustls` – same as default but with `rustls-tls` instead of `native-tls-tls`.
59//!
60//! **Example:**
61//!
62//! ```toml
63//! [dependencies]
64//! mysql_async = { version = "*", default-features = false, features = ["default-rustls"] }
65//! ```
66//!
67//! * `native-tls-tls` – enables `native-tls`-based TLS support _(conflicts with `rustls-tls`)_
68//!
69//! **Example:**
70//!
71//! ```toml
72//! [dependencies]
73//! mysql_async = { version = "*", default-features = false, features = ["native-tls-tls"] }
74//!
75//! * `rustls-tls` – enables `native-tls`-based TLS support _(conflicts with `native-tls-tls`)_
76//!
77//! **Example:**
78//!
79//! ```toml
80//! [dependencies]
81//! mysql_async = { version = "*", default-features = false, features = ["rustls-tls"] }
82//!
83//! * `tracing` – enables instrumentation via `tracing` package.
84//!
85//! Primary operations (`query`, `prepare`, `exec`) are instrumented at `INFO` level.
86//! Remaining operations, incl. `get_conn`, are instrumented at `DEBUG` level.
87//! Also at `DEBUG`, the SQL queries and parameters are added to the `query`, `prepare`
88//! and `exec` spans. Also some internal queries are instrumented at `TRACE` level.
89//!
90//! **Example:**
91//!
92//! ```toml
93//! [dependencies]
94//! mysql_async = { version = "*", features = ["tracing"] }
95//! ```
96//!
97//! * `derive` – enables `mysql_commom/derive` feature
98//!
99//! * `binlog` - enables binlog-related functionality. Enables:
100//!
101//! - `mysql_common/binlog"
102//!
103//! [myslqcommonfeatures]: https://github.com/blackbeam/rust_mysql_common#crate-features
104//!
105//! # TLS/SSL Support
106//!
107//! SSL support comes in two flavors:
108//!
109//! 1. Based on native-tls – this is the default option, that usually works without pitfalls
110//! (see the `native-tls-tls` crate feature).
111//!
112//! 2. Based on rustls – TLS backend written in Rust (see the `rustls-tls` crate feature).
113//!
114//! Please also note a few things about rustls:
115//! - it will fail if you'll try to connect to the server by its IP address,
116//! hostname is required;
117//! - it, most likely, won't work on windows, at least with default server certs,
118//! generated by the MySql installer.
119//!
120//! # Connection URL parameters
121//!
122//! There is a set of url-parameters supported by the driver (see documentation on [`Opts`]).
123//!
124//! # Example
125//!
126//! ```rust
127//! # use mysql_async::{Result, test_misc::get_opts};
128//! use mysql_async::prelude::*;
129//! # use std::env;
130//!
131//! #[derive(Debug, PartialEq, Eq, Clone)]
132//! struct Payment {
133//! customer_id: i32,
134//! amount: i32,
135//! account_name: Option<String>,
136//! }
137//!
138//! #[tokio::main]
139//! async fn main() -> Result<()> {
140//! let payments = vec![
141//! Payment { customer_id: 1, amount: 2, account_name: None },
142//! Payment { customer_id: 3, amount: 4, account_name: Some("foo".into()) },
143//! Payment { customer_id: 5, amount: 6, account_name: None },
144//! Payment { customer_id: 7, amount: 8, account_name: None },
145//! Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()) },
146//! ];
147//!
148//! let database_url = /* ... */
149//! # get_opts();
150//!
151//! let pool = mysql_async::Pool::new(database_url);
152//! let mut conn = pool.get_conn().await?;
153//!
154//! // Create a temporary table
155//! r"CREATE TEMPORARY TABLE payment (
156//! customer_id int not null,
157//! amount int not null,
158//! account_name text
159//! )".ignore(&mut conn).await?;
160//!
161//! // Save payments
162//! r"INSERT INTO payment (customer_id, amount, account_name)
163//! VALUES (:customer_id, :amount, :account_name)"
164//! .with(payments.iter().map(|payment| params! {
165//! "customer_id" => payment.customer_id,
166//! "amount" => payment.amount,
167//! "account_name" => payment.account_name.as_ref(),
168//! }))
169//! .batch(&mut conn)
170//! .await?;
171//!
172//! // Load payments from the database. Type inference will work here.
173//! let loaded_payments = "SELECT customer_id, amount, account_name FROM payment"
174//! .with(())
175//! .map(&mut conn, |(customer_id, amount, account_name)| Payment { customer_id, amount, account_name })
176//! .await?;
177//!
178//! // Dropped connection will go to the pool
179//! drop(conn);
180//!
181//! // The Pool must be disconnected explicitly because
182//! // it's an asynchronous operation.
183//! pool.disconnect().await?;
184//!
185//! assert_eq!(loaded_payments, payments);
186//!
187//! // the async fn returns Result, so
188//! Ok(())
189//! }
190//! ```
191//!
192//! # Pool
193//!
194//! The [`Pool`] structure is an asynchronous connection pool.
195//!
196//! Please note:
197//!
198//! * [`Pool`] is a smart pointer – each clone will point to the same pool instance.
199//! * [`Pool`] is `Send + Sync + 'static` – feel free to pass it around.
200//! * use [`Pool::disconnect`] to gracefuly close the pool.
201//! * ⚠️ [`Pool::new`] is lazy and won't assert server availability.
202//!
203//! # Transaction
204//!
205//! [`Conn::start_transaction`] is a wrapper, that starts with `START TRANSACTION`
206//! and ends with `COMMIT` or `ROLLBACK`.
207//!
208//! Dropped transaction will be implicitly rolled back if it wasn't explicitly
209//! committed or rolled back. Note that this behaviour will be triggered by a pool
210//! (on conn drop) or by the next query, i.e. may be delayed.
211//!
212//! API won't allow you to run nested transactions because some statements causes
213//! an implicit commit (`START TRANSACTION` is one of them), so this behavior
214//! is chosen as less error prone.
215//!
216//! # `Value`
217//!
218//! This enumeration represents the raw value of a MySql cell. Library offers conversion between
219//! `Value` and different rust types via `FromValue` trait described below.
220//!
221//! ## `FromValue` trait
222//!
223//! This trait is reexported from **mysql_common** create. Please refer to its
224//! [crate docs](https://docs.rs/mysql_common) for the list of supported conversions.
225//!
226//! Trait offers conversion in two flavours:
227//!
228//! * `from_value(Value) -> T` - convenient, but panicking conversion.
229//!
230//! Note, that for any variant of `Value` there exist a type, that fully covers its domain,
231//! i.e. for any variant of `Value` there exist `T: FromValue` such that `from_value` will never
232//! panic. This means, that if your database schema is known, than it's possible to write your
233//! application using only `from_value` with no fear of runtime panic.
234//!
235//! Also note, that some convertions may fail even though the type seem sufficient,
236//! e.g. in case of invalid dates (see [sql mode](https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html)).
237//!
238//! * `from_value_opt(Value) -> Option<T>` - non-panicking, but less convenient conversion.
239//!
240//! This function is useful to probe conversion in cases, where source database schema
241//! is unknown.
242//!
243//! # MySql query protocols
244//!
245//! ## Text protocol
246//!
247//! MySql text protocol is implemented in the set of `Queryable::query*` methods
248//! and in the [`prelude::Query`] trait if query is [`prelude::AsQuery`].
249//! It's useful when your query doesn't have parameters.
250//!
251//! **Note:** All values of a text protocol result set will be encoded as strings by the server,
252//! so `from_value` conversion may lead to additional parsing costs.
253//!
254//! ## Binary protocol and prepared statements.
255//!
256//! MySql binary protocol is implemented in the set of `exec*` methods,
257//! defined on the [`prelude::Queryable`] trait and in the [`prelude::Query`]
258//! trait if query is [`QueryWithParams`]. Prepared statements is the only way to
259//! pass rust value to the MySql server. MySql uses `?` symbol as a parameter placeholder.
260//!
261//! **Note:** it's only possible to use parameters where a single MySql value
262//! is expected, i.e. you can't execute something like `SELECT ... WHERE id IN ?`
263//! with a vector as a parameter. You'll need to build a query that looks like
264//! `SELECT ... WHERE id IN (?, ?, ...)` and to pass each vector element as
265//! a parameter.
266//!
267//! # Named parameters
268//!
269//! MySql itself doesn't have named parameters support, so it's implemented on the client side.
270//! One should use `:name` as a placeholder syntax for a named parameter. Named parameters uses
271//! the following naming convention:
272//!
273//! * parameter name must start with either `_` or `a..z`
274//! * parameter name may continue with `_`, `a..z` and `0..9`
275//!
276//! **Note:** this rules mean that, say, the statment `SELECT :fooBar` will be translated
277//! to `SELECT ?Bar` so please be careful.
278//!
279//! Named parameters may be repeated within the statement, e.g `SELECT :foo, :foo` will require
280//! a single named parameter `foo` that will be repeated on the corresponding positions during
281//! statement execution.
282//!
283//! One should use the `params!` macro to build parameters for execution.
284//!
285//! **Note:** Positional and named parameters can't be mixed within the single statement.
286//!
287//! # Statements
288//!
289//! In MySql each prepared statement belongs to a particular connection and can't be executed
290//! on another connection. Trying to do so will lead to an error. The driver won't tie statement
291//! to its connection in any way, but one can look on to the connection id, contained
292//! in the [`Statement`] structure.
293//!
294//! # LOCAL INFILE Handlers
295//!
296//! **Warning:** You should be aware of [Security Considerations for LOAD DATA LOCAL][1].
297//!
298//! There are two flavors of LOCAL INFILE handlers – _global_ and _local_.
299//!
300//! I case of a LOCAL INFILE request from the server the driver will try to find a handler for it:
301//!
302//! 1. It'll try to use _local_ handler installed on the connection, if any;
303//! 2. It'll try to use _global_ handler, specified via [`OptsBuilder::local_infile_handler`],
304//! if any;
305//! 3. It will emit [`LocalInfileError::NoHandler`] if no handlers found.
306//!
307//! The purpose of a handler (_local_ or _global_) is to return [`InfileData`].
308//!
309//! ## _Global_ LOCAL INFILE handler
310//!
311//! See [`prelude::GlobalHandler`].
312//!
313//! Simply speaking the _global_ handler is an async function that takes a file name (as `&[u8]`)
314//! and returns `Result<InfileData>`.
315//!
316//! You can set it up using [`OptsBuilder::local_infile_handler`]. Server will use it if there is no
317//! _local_ handler installed for the connection. This handler might be called multiple times.
318//!
319//! Examles:
320//!
321//! 1. [`WhiteListFsHandler`] is a _global_ handler.
322//! 2. Every `T: Fn(&[u8]) -> BoxFuture<'static, Result<InfileData, LocalInfileError>>`
323//! is a _global_ handler.
324//!
325//! ## _Local_ LOCAL INFILE handler.
326//!
327//! Simply speaking the _local_ handler is a future, that returns `Result<InfileData>`.
328//!
329//! This is a one-time handler – it's consumed after use. You can set it up using
330//! [`Conn::set_infile_handler`]. This handler have priority over _global_ handler.
331//!
332//! Worth noting:
333//!
334//! 1. `impl Drop for Conn` will clear _local_ handler, i.e. handler will be removed when
335//! connection is returned to a `Pool`.
336//! 2. [`Conn::reset`] will clear _local_ handler.
337//!
338//! Example:
339//!
340//! ```rust
341//! # use mysql_async::{prelude::*, test_misc::get_opts, OptsBuilder, Result, Error};
342//! # use futures_util::future::FutureExt;
343//! # use futures_util::stream::{self, StreamExt};
344//! # use bytes::Bytes;
345//! # use std::env;
346//! # #[tokio::main]
347//! # async fn main() -> Result<()> {
348//! #
349//! # let database_url = get_opts();
350//! let pool = mysql_async::Pool::new(database_url);
351//!
352//! let mut conn = pool.get_conn().await?;
353//! "CREATE TEMPORARY TABLE tmp (id INT, val TEXT)".ignore(&mut conn).await?;
354//!
355//! // We are going to call `LOAD DATA LOCAL` so let's setup a one-time handler.
356//! conn.set_infile_handler(async move {
357//! // We need to return a stream of `io::Result<Bytes>`
358//! Ok(stream::iter([Bytes::from("1,a\r\n"), Bytes::from("2,b\r\n3,c")]).map(Ok).boxed())
359//! });
360//!
361//! let result = r#"LOAD DATA LOCAL INFILE 'whatever'
362//! INTO TABLE `tmp`
363//! FIELDS TERMINATED BY ',' ENCLOSED BY '\"'
364//! LINES TERMINATED BY '\r\n'"#.ignore(&mut conn).await;
365//!
366//! match result {
367//! Ok(()) => (),
368//! Err(Error::Server(ref err)) if err.code == 1148 => {
369//! // The used command is not allowed with this MySQL version
370//! return Ok(());
371//! },
372//! Err(Error::Server(ref err)) if err.code == 3948 => {
373//! // Loading local data is disabled;
374//! // this must be enabled on both the client and the server
375//! return Ok(());
376//! }
377//! e @ Err(_) => e.unwrap(),
378//! }
379//!
380//! // Now let's verify the result
381//! let result: Vec<(u32, String)> = conn.query("SELECT * FROM tmp ORDER BY id ASC").await?;
382//! assert_eq!(
383//! result,
384//! vec![(1, "a".into()), (2, "b".into()), (3, "c".into())]
385//! );
386//!
387//! drop(conn);
388//! pool.disconnect().await?;
389//! # Ok(())
390//! # }
391//! ```
392//!
393//! [1]: https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html
394//!
395//! # Testing
396//!
397//! Tests uses followin environment variables:
398//! * `DATABASE_URL` – defaults to `mysql://root:password@127.0.0.1:3307/mysql`
399//! * `COMPRESS` – set to `1` or `true` to enable compression for tests
400//! * `SSL` – set to `1` or `true` to enable TLS for tests
401//!
402//! You can run a test server using doker. Please note that params related
403//! to max allowed packet, local-infile and binary logging are required
404//! to properly run tests (please refer to `azure-pipelines.yml`):
405//!
406//! ```sh
407//! docker run -d --name container \
408//! -v `pwd`:/root \
409//! -p 3307:3306 \
410//! -e MYSQL_ROOT_PASSWORD=password \
411//! mysql:8.0 \
412//! --max-allowed-packet=36700160 \
413//! --local-infile \
414//! --log-bin=mysql-bin \
415//! --log-slave-updates \
416//! --gtid_mode=ON \
417//! --enforce_gtid_consistency=ON \
418//! --server-id=1
419//! ```
420//!
421
422#![recursion_limit = "1024"]
423#![cfg_attr(feature = "nightly", feature(test))]
424
425#[cfg(feature = "nightly")]
426extern crate test;
427
428#[cfg(feature = "derive")]
429extern crate mysql_common;
430
431pub use mysql_common::{constants as consts, params};
432
433use std::sync::Arc;
434
435mod buffer_pool;
436
437#[macro_use]
438mod tracing_utils;
439
440#[macro_use]
441mod macros;
442mod conn;
443mod connection_like;
444/// Errors used in this crate
445mod error;
446mod io;
447mod local_infile_handler;
448mod opts;
449mod query;
450mod queryable;
451
452type BoxFuture<'a, T> = futures_core::future::BoxFuture<'a, Result<T>>;
453
454static BUFFER_POOL: once_cell::sync::Lazy<Arc<crate::buffer_pool::BufferPool>> =
455 once_cell::sync::Lazy::new(Default::default);
456
457#[cfg(feature = "binlog")]
458#[doc(inline)]
459pub use self::conn::binlog_stream::{request::BinlogStreamRequest, BinlogStream};
460
461#[doc(inline)]
462pub use self::conn::Conn;
463
464#[doc(inline)]
465pub use self::conn::pool::Pool;
466
467#[doc(inline)]
468pub use self::error::{
469 DriverError, Error, IoError, LocalInfileError, ParseError, Result, ServerError, UrlError,
470};
471
472#[doc(inline)]
473pub use self::query::QueryWithParams;
474
475#[doc(inline)]
476pub use self::queryable::transaction::IsolationLevel;
477
478#[doc(inline)]
479#[cfg(any(feature = "rustls", feature = "native-tls"))]
480pub use self::opts::ClientIdentity;
481
482#[doc(inline)]
483pub use self::opts::{
484 ChangeUserOpts, Opts, OptsBuilder, PoolConstraints, PoolOpts, SslOpts,
485 DEFAULT_INACTIVE_CONNECTION_TTL, DEFAULT_POOL_CONSTRAINTS, DEFAULT_STMT_CACHE_SIZE,
486 DEFAULT_TTL_CHECK_INTERVAL,
487};
488
489#[doc(inline)]
490pub use self::local_infile_handler::{builtin::WhiteListFsHandler, InfileData};
491
492#[doc(inline)]
493pub use mysql_common::packets::{
494 session_state_change::{
495 Gtids, Schema, SessionStateChange, SystemVariable, TransactionCharacteristics,
496 TransactionState, Unsupported,
497 },
498 Column, GnoInterval, OkPacket, SessionStateInfo, Sid,
499};
500
501#[cfg(feature = "binlog")]
502pub mod binlog {
503 #[doc(inline)]
504 pub use mysql_common::binlog::consts::*;
505
506 #[doc(inline)]
507 pub use mysql_common::binlog::{events, jsonb, jsondiff, row, value};
508}
509
510#[doc(inline)]
511pub use mysql_common::proto::codec::Compression;
512
513#[doc(inline)]
514pub use mysql_common::row::Row;
515
516#[doc(inline)]
517pub use mysql_common::params::Params;
518
519#[doc(inline)]
520pub use mysql_common::value::Value;
521
522#[doc(inline)]
523pub use mysql_common::row::convert::{from_row, from_row_opt, FromRowError};
524
525#[doc(inline)]
526pub use mysql_common::value::convert::{from_value, from_value_opt, FromValueError};
527
528#[doc(inline)]
529pub use mysql_common::value::json::{Deserialized, Serialized};
530
531#[doc(inline)]
532pub use self::queryable::query_result::{result_set_stream::ResultSetStream, QueryResult};
533
534#[doc(inline)]
535pub use self::queryable::transaction::{Transaction, TxOpts};
536
537#[doc(inline)]
538pub use self::queryable::{BinaryProtocol, TextProtocol};
539
540#[doc(inline)]
541pub use self::queryable::stmt::Statement;
542
543/// Futures used in this crate
544pub mod futures {
545 pub use crate::conn::pool::futures::{DisconnectPool, GetConn};
546}
547
548/// Traits used in this crate
549pub mod prelude {
550 #[doc(inline)]
551 pub use crate::local_infile_handler::GlobalHandler;
552 #[doc(inline)]
553 pub use crate::query::AsQuery;
554 #[doc(inline)]
555 pub use crate::query::{BatchQuery, Query, WithParams};
556 #[doc(inline)]
557 pub use crate::queryable::Queryable;
558 #[doc(inline)]
559 pub use mysql_common::prelude::FromRow;
560 #[doc(inline)]
561 pub use mysql_common::prelude::{FromValue, ToValue};
562
563 /// Everything that is a statement.
564 ///
565 /// ```no_run
566 /// # use std::{borrow::Cow, sync::Arc};
567 /// # use mysql_async::{Statement, prelude::StatementLike};
568 /// fn type_is_a_stmt<T: StatementLike>() {}
569 ///
570 /// type_is_a_stmt::<Cow<'_, str>>();
571 /// type_is_a_stmt::<&'_ str>();
572 /// type_is_a_stmt::<String>();
573 /// type_is_a_stmt::<Box<str>>();
574 /// type_is_a_stmt::<Arc<str>>();
575 /// type_is_a_stmt::<Statement>();
576 ///
577 /// fn ref_to_a_clonable_stmt_is_also_a_stmt<T: StatementLike + Clone>() {
578 /// type_is_a_stmt::<&T>();
579 /// }
580 /// ```
581 pub trait StatementLike: crate::queryable::stmt::StatementLike {}
582 impl<T: crate::queryable::stmt::StatementLike> StatementLike for T {}
583
584 /// Everything that is a connection.
585 ///
586 /// Note that you could obtain a `'static` connection by giving away `Conn` or `Pool`.
587 pub trait ToConnection<'a, 't: 'a>: crate::connection_like::ToConnection<'a, 't> {}
588 // explicitly implemented because of rusdoc
589 impl<'a> ToConnection<'a, 'static> for &'a crate::Pool {}
590 impl ToConnection<'static, 'static> for crate::Pool {}
591 impl ToConnection<'static, 'static> for crate::Conn {}
592 impl<'a> ToConnection<'a, 'static> for &'a mut crate::Conn {}
593 impl<'a, 't> ToConnection<'a, 't> for &'a mut crate::Transaction<'t> {}
594
595 /// Trait for protocol markers [`crate::TextProtocol`] and [`crate::BinaryProtocol`].
596 pub trait Protocol: crate::queryable::Protocol {}
597 impl Protocol for crate::BinaryProtocol {}
598 impl Protocol for crate::TextProtocol {}
599
600 pub use mysql_common::params;
601}
602
603#[doc(hidden)]
604pub mod test_misc {
605 use lazy_static::lazy_static;
606
607 use std::env;
608
609 use crate::opts::{Opts, OptsBuilder, SslOpts};
610
611 #[allow(dead_code)]
612 #[allow(unreachable_code)]
613 fn error_should_implement_send_and_sync() {
614 fn _dummy<T: Send + Sync + Unpin>(_: T) {}
615 #[allow(unused_variables)]
616 let err: crate::Error = panic!();
617 _dummy(err);
618 }
619
620 lazy_static! {
621 pub static ref DATABASE_URL: String = {
622 if let Ok(url) = env::var("DATABASE_URL") {
623 let opts = Opts::from_url(&url).expect("DATABASE_URL invalid");
624 if opts
625 .db_name()
626 .expect("a database name is required")
627 .is_empty()
628 {
629 panic!("database name is empty");
630 }
631 url
632 } else {
633 "mysql://root:password@localhost:3307/mysql".into()
634 }
635 };
636 }
637
638 pub fn get_opts() -> OptsBuilder {
639 let mut builder = OptsBuilder::from_opts(Opts::from_url(&DATABASE_URL).unwrap());
640 if test_ssl() {
641 let ssl_opts = SslOpts::default()
642 .with_danger_skip_domain_validation(true)
643 .with_danger_accept_invalid_certs(true);
644 builder = builder.prefer_socket(false).ssl_opts(ssl_opts);
645 }
646 if test_compression() {
647 builder = builder.compression(crate::Compression::default());
648 }
649 builder
650 }
651
652 pub fn test_compression() -> bool {
653 ["true", "1"].contains(&&*env::var("COMPRESS").unwrap_or_default())
654 }
655
656 pub fn test_ssl() -> bool {
657 ["true", "1"].contains(&&*env::var("SSL").unwrap_or_default())
658 }
659}