├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── README.tpl ├── azure-pipelines.yml ├── build.rs ├── src ├── buffer_pool │ ├── disabled.rs │ ├── enabled.rs │ └── mod.rs ├── conn │ ├── binlog_stream.rs │ ├── local_infile.rs │ ├── mod.rs │ ├── opts │ │ ├── mod.rs │ │ ├── native_tls_opts.rs │ │ ├── pool_opts.rs │ │ └── rustls_opts.rs │ ├── pool │ │ ├── inner.rs │ │ └── mod.rs │ ├── query.rs │ ├── query_result.rs │ ├── queryable.rs │ ├── stmt.rs │ ├── stmt_cache.rs │ └── transaction.rs ├── error │ ├── mod.rs │ └── tls │ │ ├── mod.rs │ │ ├── native_tls_error.rs │ │ └── rustls_error.rs ├── io │ ├── mod.rs │ ├── tcp.rs │ └── tls │ │ ├── mod.rs │ │ ├── native_tls_io.rs │ │ └── rustls_io.rs └── lib.rs └── tests ├── ca-key.pem ├── ca.crt ├── client-identity.p12 ├── client-key.der ├── client-key.pem ├── client-key.pkcs8.der ├── client-key.pkcs8.pem ├── client.crt ├── server-key.pem └── server.crt /.gitignore: -------------------------------------------------------------------------------- 1 | doc/ 2 | target/ 3 | tests/rust-mysql-simple-test 4 | *.racertmp 5 | Cargo.lock 6 | .idea 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mysql" 3 | version = "26.0.0" 4 | authors = ["blackbeam"] 5 | description = "Mysql client library implemented in rust" 6 | license = "MIT/Apache-2.0" 7 | documentation = "https://docs.rs/mysql" 8 | repository = "https://github.com/blackbeam/rust-mysql-simple" 9 | keywords = ["database", "sql"] 10 | exclude = ["tests/*", ".*", "Makefile"] 11 | categories = ["database"] 12 | edition = "2021" 13 | build = "build.rs" 14 | 15 | [badges.azure-devops] 16 | build = "1" 17 | pipeline = "blackbeam.rust-mysql-simple" 18 | project = "aikorsky/mysql Rust" 19 | 20 | [lib] 21 | name = "mysql" 22 | path = "src/lib.rs" 23 | 24 | [profile.bench] 25 | debug = true 26 | 27 | [features] 28 | default = ["minimal", "derive", "buffer-pool"] 29 | default-rust = ["minimal-rust", "derive", "buffer-pool"] 30 | 31 | # minimal feature set with system flate2 impl 32 | minimal = ["flate2/zlib"] 33 | # minimal feature set with rust flate2 impl 34 | minimal-rust = ["flate2/rust_backend"] 35 | 36 | # native TLS backend 37 | native-tls = ["dep:native-tls"] 38 | # rustls TLS backend with aws_lc_rs provider 39 | rustls-tls = ["rustls", "rustls/aws_lc_rs"] 40 | # rustls TLS backend with ring provider 41 | rustls-tls-ring = ["rustls", "rustls/ring"] 42 | # rustls TLS backend (no provider) 43 | rustls = [ 44 | "dep:rustls", 45 | "rustls/logging", 46 | "rustls/std", 47 | "rustls/tls12", 48 | "webpki", 49 | "webpki-roots", 50 | "rustls-pemfile", 51 | ] 52 | 53 | # global buffer pool 54 | buffer-pool = [] 55 | nightly = [] 56 | 57 | # mysql_common features 58 | derive = ["mysql_common/derive"] 59 | chrono = ["mysql_common/chrono"] 60 | time = ["mysql_common/time"] 61 | bigdecimal = ["mysql_common/bigdecimal"] 62 | rust_decimal = ["mysql_common/rust_decimal"] 63 | frunk = ["mysql_common/frunk"] 64 | binlog = ["mysql_common/binlog"] 65 | client_ed25519 = ["mysql_common/client_ed25519"] 66 | 67 | [dev-dependencies] 68 | mysql_common = { version = "0.35.4", features = ["time", "frunk"] } 69 | rand = "0.8.2" 70 | serde = { version = "1", features = ["derive"] } 71 | serde_json = "1" 72 | time = "0.3" 73 | frunk = "0.4" 74 | 75 | [dependencies] 76 | bufstream = "~0.1" 77 | bytes = "1.0.1" 78 | crossbeam-queue = "0.3.12" 79 | flate2 = { version = "1.0", default-features = false } 80 | io-enum = "1.0.0" 81 | lru = { version = "0.12", default-features = false } 82 | mysql_common = { version = "0.35.4", default-features = false } 83 | native-tls = { version = "0.2.3", optional = true } 84 | pem = "3" 85 | percent-encoding = "2.1.0" 86 | rustls = { version = "0.23", optional = true, default-features = false } 87 | rustls-pemfile = { version = "2.1", optional = true } 88 | socket2 = "0.5.2" 89 | twox-hash = { version = "2", default-features = false, features = ["xxhash64"] } 90 | url = "2.1" 91 | webpki = { version = "0.22", features = ["std"], optional = true } 92 | webpki-roots = { version = "0.26", optional = true } 93 | 94 | [target.'cfg(target_os = "windows")'.dependencies] 95 | named_pipe = "~0.4" 96 | 97 | [target.'cfg(unix)'.dependencies] 98 | libc = "0.2" 99 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 rust-mysql-common contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) 2 | mkfile_dir := $(dir $(mkfile_path)) 3 | MYSQL_DATA_DIR = $(mkfile_dir)tests/rust-mysql-simple-test 4 | MYSQL_SSL_CA = $(mkfile_dir)tests/ca.crt 5 | MYSQL_SSL_CERT = $(mkfile_dir)tests/server.crt 6 | MYSQL_SSL_KEY = $(mkfile_dir)tests/server-key.pem 7 | MYSQL_PORT = 3307 8 | BASEDIR := $(shell mysqld --verbose --help 2>/dev/null | grep -e '^basedir' | awk '{ print $$2 }') 9 | OS := $(shell uname) 10 | 11 | FEATURES := "ssl" 12 | BENCH_FEATURES := "nightly" "nightly ssl" 13 | 14 | define run-mysql 15 | if [ -e $(MYSQL_DATA_DIR)/mysqld.pid ];\ 16 | then \ 17 | kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid`; \ 18 | rm -rf $(MYSQL_DATA_DIR) || true; \ 19 | fi 20 | 21 | if [ -e $(MYSQL_DATA_DIR) ];\ 22 | then \ 23 | rm -rf $(MYSQL_DATA_DIR) || true; \ 24 | fi 25 | 26 | mkdir -p $(MYSQL_DATA_DIR)/data 27 | 28 | if ((mysql --version | grep -P '5\.(6|7)' >>/dev/null) || (mysql --version | grep Maria >>/dev/null));\ 29 | then \ 30 | mysql_install_db --no-defaults \ 31 | --basedir=$(BASEDIR) \ 32 | --datadir=$(MYSQL_DATA_DIR)/data; \ 33 | else \ 34 | mysqld --initialize-insecure \ 35 | --basedir=$(BASEDIR) \ 36 | --datadir=$(MYSQL_DATA_DIR)/data; \ 37 | fi 38 | 39 | mysqld --no-defaults \ 40 | --basedir=$(BASEDIR) \ 41 | --bind-address=127.0.0.1 \ 42 | --datadir=$(MYSQL_DATA_DIR)/data \ 43 | --max-allowed-packet=32M \ 44 | --pid-file=$(MYSQL_DATA_DIR)/mysqld.pid \ 45 | --port=$(MYSQL_PORT) \ 46 | --innodb_file_per_table=1 \ 47 | --innodb_log_file_size=256M \ 48 | --local-infile=ON \ 49 | --gtid_mode=ON \ 50 | --enforce_gtid_consistency=ON \ 51 | --ssl \ 52 | --ssl-ca=$(MYSQL_SSL_CA) \ 53 | --ssl-cert=$(MYSQL_SSL_CERT) \ 54 | --ssl-key=$(MYSQL_SSL_KEY) \ 55 | --socket=$(MYSQL_DATA_DIR)/mysqld.sock & 56 | 57 | while ! nc -z 127.0.0.1 $(MYSQL_PORT); \ 58 | do \ 59 | sleep 0.5; \ 60 | done 61 | 62 | if [ -e ~/.mysql_secret ]; \ 63 | then \ 64 | mysqladmin -h127.0.0.1 \ 65 | --port=$(MYSQL_PORT) \ 66 | -u root \ 67 | -p"`cat ~/.mysql_secret | grep -v Password`" password 'password'; \ 68 | else \ 69 | mysqladmin -h127.0.0.1 --port=$(MYSQL_PORT) -u root password 'password'; \ 70 | fi 71 | endef 72 | 73 | all: lib doc 74 | 75 | target/deps: lib 76 | 77 | target/tests/mysql: test 78 | 79 | lib: 80 | cargo build --release 81 | 82 | doc: 83 | cargo doc 84 | 85 | test: 86 | $(run-mysql) 87 | if ! (COMPRESS=0 SSL=0 cargo test --no-default-features --features minimal,time,frunk); \ 88 | then \ 89 | kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid`; \ 90 | rm -rf $(MYSQL_DATA_DIR) || true; \ 91 | exit 1; \ 92 | fi 93 | if ! (COMPRESS=0 SSL=0 cargo test); \ 94 | then \ 95 | kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid`; \ 96 | rm -rf $(MYSQL_DATA_DIR) || true; \ 97 | exit 1; \ 98 | fi 99 | if ! (COMPRESS=1 SSL=0 cargo test); \ 100 | then \ 101 | kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid`; \ 102 | rm -rf $(MYSQL_DATA_DIR) || true; \ 103 | exit 1; \ 104 | fi 105 | if ! (COMPRESS=0 SSL=1 cargo test); \ 106 | then \ 107 | kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid`; \ 108 | rm -rf $(MYSQL_DATA_DIR) || true; \ 109 | exit 1; \ 110 | fi 111 | if ! (COMPRESS=1 SSL=1 cargo test --no-default-features --features default-rustls); \ 112 | then \ 113 | kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid`; \ 114 | rm -rf $(MYSQL_DATA_DIR) || true; \ 115 | exit 1; \ 116 | fi 117 | 118 | @kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid` 119 | @rm -rf $(MYSQL_DATA_DIR) || true 120 | 121 | bench: 122 | $(run-mysql) 123 | for var in $(BENCH_FEATURES); \ 124 | do \ 125 | echo TESTING FEATURS: $$var; \ 126 | if ! (cargo bench --no-default-features --features "$$var"); \ 127 | then \ 128 | kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid`; \ 129 | rm -rf $(MYSQL_DATA_DIR) || true; \ 130 | exit 1; \ 131 | fi \ 132 | done 133 | 134 | @kill -9 `cat $(MYSQL_DATA_DIR)/mysqld.pid` 135 | @rm -rf $(MYSQL_DATA_DIR) || true 136 | 137 | clean: 138 | cargo clean 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gitter](https://badges.gitter.im/rust-mysql/community.svg)](https://gitter.im/rust-mysql/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/mysql.svg)](https://crates.io/crates/mysql) 4 | [![Build Status](https://dev.azure.com/aikorsky/mysql%20Rust/_apis/build/status/blackbeam%2Erust%2Dmysql%2Dsimple)](https://dev.azure.com/aikorsky/mysql%20Rust/_build/latest?definitionId=1) 5 | 6 | # mysql 7 | 8 | This crate offers: 9 | 10 | * MySql database driver in pure rust; 11 | * connection pool. 12 | 13 | Features: 14 | 15 | * macOS, Windows and Linux support; 16 | * TLS support via **native-tls** or **rustls** (see the [SSL Support](#ssl-support) section); 17 | * MySql text protocol support, i.e. support of simple text queries and text result sets; 18 | * MySql binary protocol support, i.e. support of prepared statements and binary result sets; 19 | * support of multi-result sets; 20 | * support of named parameters for prepared statements (see the [Named Parameters](#named-parameters) section); 21 | * per-connection cache of prepared statements (see the [Statement Cache](#statement-cache) section); 22 | * buffer pool (see the [Buffer Pool](#buffer-pool) section); 23 | * support of MySql packets larger than 2^24; 24 | * support of Unix sockets and Windows named pipes; 25 | * support of custom LOCAL INFILE handlers; 26 | * support of MySql protocol compression; 27 | * support of auth plugins: 28 | * **mysql_native_password** - for MySql prior to v8; 29 | * **caching_sha2_password** - for MySql v8 and higher; 30 | * **mysql_clear_password** - opt-in (see [`Opts::get_enable_cleartext_plugin`]. 31 | 32 | ### Installation 33 | 34 | Put the desired version of the crate into the `dependencies` section of your `Cargo.toml`: 35 | 36 | ```toml 37 | [dependencies] 38 | mysql = "*" 39 | ``` 40 | 41 | ### Example 42 | 43 | ```rust 44 | use mysql::*; 45 | use mysql::prelude::*; 46 | 47 | #[derive(Debug, PartialEq, Eq)] 48 | struct Payment { 49 | customer_id: i32, 50 | amount: i32, 51 | account_name: Option, 52 | } 53 | 54 | 55 | fn main() -> std::result::Result<(), Box> { 56 | let url = "mysql://root:password@localhost:3307/db_name"; 57 | # Opts::try_from(url)?; 58 | # let url = get_opts(); 59 | let pool = Pool::new(url)?; 60 | 61 | let mut conn = pool.get_conn()?; 62 | 63 | // Let's create a table for payments. 64 | conn.query_drop( 65 | r"CREATE TEMPORARY TABLE payment ( 66 | customer_id int not null, 67 | amount int not null, 68 | account_name text 69 | )")?; 70 | 71 | let payments = vec![ 72 | Payment { customer_id: 1, amount: 2, account_name: None }, 73 | Payment { customer_id: 3, amount: 4, account_name: Some("foo".into()) }, 74 | Payment { customer_id: 5, amount: 6, account_name: None }, 75 | Payment { customer_id: 7, amount: 8, account_name: None }, 76 | Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()) }, 77 | ]; 78 | 79 | // Now let's insert payments to the database 80 | conn.exec_batch( 81 | r"INSERT INTO payment (customer_id, amount, account_name) 82 | VALUES (:customer_id, :amount, :account_name)", 83 | payments.iter().map(|p| params! { 84 | "customer_id" => p.customer_id, 85 | "amount" => p.amount, 86 | "account_name" => &p.account_name, 87 | }) 88 | )?; 89 | 90 | // Let's select payments from database. Type inference should do the trick here. 91 | let selected_payments = conn 92 | .query_map( 93 | "SELECT customer_id, amount, account_name from payment", 94 | |(customer_id, amount, account_name)| { 95 | Payment { customer_id, amount, account_name } 96 | }, 97 | )?; 98 | 99 | // Let's make sure, that `payments` equals to `selected_payments`. 100 | // Mysql gives no guaranties on order of returned rows 101 | // without `ORDER BY`, so assume we are lucky. 102 | assert_eq!(payments, selected_payments); 103 | println!("Yay!"); 104 | 105 | Ok(()) 106 | } 107 | ``` 108 | 109 | ### Crate Features 110 | 111 | * feature sets: 112 | 113 | * **default** – includes `buffer-pool` `flate2/zlib` and `derive` 114 | * **default-rust** - same as `default` but with `flate2/rust_backend` instead of `flate2/zlib` 115 | * **minimal** - includes `flate2/zlib` only 116 | * **minimal-rust** - includes `flate2/rust_backend` only 117 | 118 | * features: 119 | * **buffer-pool** – enables buffer pooling 120 | (see the [Buffer Pool](#buffer-pool) section) 121 | * **derive** – reexports derive macros under `prelude` 122 | (see [corresponding section][derive_docs] in the `mysql_common` documentation) 123 | 124 | * TLS/SSL related features: 125 | 126 | * **native-tls** – specifies `native-tls` as the TLS backend 127 | (see the [SSL Support](#ssl-support) section) 128 | * **rustls-tls** – specifies `rustls` as the TLS backend using `aws-lc-rs` crypto provider 129 | (see the [SSL Support](#ssl-support) section) 130 | * **rustls-tls-ring** – specifies `rustls` as the TLS backend using `ring` crypto provider 131 | (see the [SSL Support](#ssl-support) section) 132 | * **rustls** - specifies `rustls` as the TLS backend without crypto provider 133 | (see the [SSL Support](#ssl-support) section) 134 | 135 | * features proxied from `mysql_common`: 136 | 137 | * **derive** - see [this table][common_features]. 138 | * **chrono** - see [this table][common_features]. 139 | * **time** - see [this table][common_features]. 140 | * **bigdecimal** - see [this table][common_features]. 141 | * **rust_decimal** - see [this table][common_features]. 142 | * **frunk** - see [this table][common_features]. 143 | * **binlog** - see [this table][common_features]. 144 | 145 | Please note, that you'll need to reenable required features if you are using `default-features = false`: 146 | 147 | ```toml 148 | [dependencies] 149 | # Lets say that we want to use only the `rustls-tls` feature: 150 | mysql = { version = "*", default-features = false, features = ["minimal-rust", "rustls-tls"] } 151 | ``` 152 | 153 | ### API Documentation 154 | 155 | Please refer to the [crate docs]. 156 | 157 | ### Basic structures 158 | 159 | #### `Opts` 160 | 161 | This structure holds server host name, client username/password and other settings, 162 | that controls client behavior. 163 | 164 | ##### URL-based connection string 165 | 166 | Note, that you can use URL-based connection string as a source of an `Opts` instance. 167 | URL schema must be `mysql`. Host, port and credentials, as well as query parameters, 168 | should be given in accordance with the RFC 3986. 169 | 170 | Examples: 171 | 172 | ```rust 173 | let _ = Opts::from_url("mysql://localhost/some_db")?; 174 | let _ = Opts::from_url("mysql://[::1]/some_db")?; 175 | let _ = Opts::from_url("mysql://user:pass%20word@127.0.0.1:3307/some_db?")?; 176 | ``` 177 | 178 | Supported URL parameters (for the meaning of each field please refer to the docs on `Opts` 179 | structure in the create API docs): 180 | 181 | * `user: string` – MySql client user name 182 | * `password: string` – MySql client password; 183 | * `db_name: string` – MySql database name; 184 | * `host: Host` – MySql server hostname/ip; 185 | * `port: u16` – MySql server port; 186 | * `pool_min: usize` – see [`PoolConstraints::min`]; 187 | * `pool_max: usize` – see [`PoolConstraints::max`]; 188 | * `prefer_socket: true | false` - see [`Opts::get_prefer_socket`]; 189 | * `tcp_keepalive_time_ms: u32` - defines the value (in milliseconds) 190 | of the `tcp_keepalive_time` field in the `Opts` structure; 191 | * `tcp_keepalive_probe_interval_secs: u32` - defines the value 192 | of the `tcp_keepalive_probe_interval_secs` field in the `Opts` structure; 193 | * `tcp_keepalive_probe_count: u32` - defines the value 194 | of the `tcp_keepalive_probe_count` field in the `Opts` structure; 195 | * `tcp_connect_timeout_ms: u64` - defines the value (in milliseconds) 196 | of the `tcp_connect_timeout` field in the `Opts` structure; 197 | * `tcp_user_timeout_ms` - defines the value (in milliseconds) 198 | of the `tcp_user_timeout` field in the `Opts` structure; 199 | * `stmt_cache_size: u32` - defines the value of the same field in the `Opts` structure; 200 | * `enable_cleartext_plugin` – see [`Opts::get_enable_cleartext_plugin`]; 201 | * `secure_auth` – see [`Opts::get_secure_auth`]; 202 | * `reset_connection` – see [`PoolOpts::reset_connection`]; 203 | * `check_health` – see [`PoolOpts::check_health`]; 204 | * `compress` - defines the value of the same field in the `Opts` structure. 205 | Supported value are: 206 | * `true` - enables compression with the default compression level; 207 | * `fast` - enables compression with "fast" compression level; 208 | * `best` - enables compression with "best" compression level; 209 | * `1`..`9` - enables compression with the given compression level. 210 | * `socket` - socket path on UNIX, or pipe name on Windows. 211 | 212 | #### `OptsBuilder` 213 | 214 | It's a convenient builder for the `Opts` structure. It defines setters for fields 215 | of the `Opts` structure. 216 | 217 | ```rust 218 | let opts = OptsBuilder::new() 219 | .user(Some("foo")) 220 | .db_name(Some("bar")); 221 | let _ = Conn::new(opts)?; 222 | ``` 223 | 224 | #### `Conn` 225 | 226 | This structure represents an active MySql connection. It also holds statement cache 227 | and metadata for the last result set. 228 | 229 | Conn's destructor will gracefully disconnect it from the server. 230 | 231 | #### `Transaction` 232 | 233 | It's a simple wrapper on top of a routine, that starts with `START TRANSACTION` 234 | and ends with `COMMIT` or `ROLLBACK`. 235 | 236 | ```rust 237 | use mysql::*; 238 | use mysql::prelude::*; 239 | 240 | let pool = Pool::new(get_opts())?; 241 | let mut conn = pool.get_conn()?; 242 | 243 | let mut tx = conn.start_transaction(TxOpts::default())?; 244 | tx.query_drop("CREATE TEMPORARY TABLE tmp (TEXT a)")?; 245 | tx.exec_drop("INSERT INTO tmp (a) VALUES (?)", ("foo",))?; 246 | let val: Option = tx.query_first("SELECT a from tmp")?; 247 | assert_eq!(val.unwrap(), "foo"); 248 | // Note, that transaction will be rolled back implicitly on Drop, if not committed. 249 | tx.rollback(); 250 | 251 | let val: Option = conn.query_first("SELECT a from tmp")?; 252 | assert_eq!(val, None); 253 | ``` 254 | 255 | #### `Pool` 256 | 257 | It's a reference to a connection pool, that can be cloned and shared between threads. 258 | 259 | ```rust 260 | use mysql::*; 261 | use mysql::prelude::*; 262 | 263 | use std::thread::spawn; 264 | 265 | let pool = Pool::new(get_opts())?; 266 | 267 | let handles = (0..4).map(|i| { 268 | spawn({ 269 | let pool = pool.clone(); 270 | move || { 271 | let mut conn = pool.get_conn()?; 272 | conn.exec_first::("SELECT ? * 10", (i,)) 273 | .map(Option::unwrap) 274 | } 275 | }) 276 | }); 277 | 278 | let result: Result> = handles.map(|handle| handle.join().unwrap()).collect(); 279 | 280 | assert_eq!(result.unwrap(), vec![0, 10, 20, 30]); 281 | ``` 282 | 283 | #### `Statement` 284 | 285 | Statement, actually, is just an identifier coupled with statement metadata, i.e an information 286 | about its parameters and columns. Internally the `Statement` structure also holds additional 287 | data required to support named parameters (see bellow). 288 | 289 | ```rust 290 | use mysql::*; 291 | use mysql::prelude::*; 292 | 293 | let pool = Pool::new(get_opts())?; 294 | let mut conn = pool.get_conn()?; 295 | 296 | let stmt = conn.prep("DO ?")?; 297 | 298 | // The prepared statement will return no columns. 299 | assert!(stmt.columns().is_empty()); 300 | 301 | // The prepared statement have one parameter. 302 | let param = stmt.params().get(0).unwrap(); 303 | assert_eq!(param.schema_str(), ""); 304 | assert_eq!(param.table_str(), ""); 305 | assert_eq!(param.name_str(), "?"); 306 | ``` 307 | 308 | #### `Value` 309 | 310 | This enumeration represents the raw value of a MySql cell. Library offers conversion between 311 | `Value` and different rust types via `FromValue` trait described below. 312 | 313 | ##### `FromValue` trait 314 | 315 | This trait is reexported from **mysql_common** create. Please refer to its 316 | [crate docs][mysql_common docs] for the list of supported conversions. 317 | 318 | Trait offers conversion in two flavours: 319 | 320 | * `from_value(Value) -> T` - convenient, but panicking conversion. 321 | 322 | Note, that for any variant of `Value` there exist a type, that fully covers its domain, 323 | i.e. for any variant of `Value` there exist `T: FromValue` such that `from_value` will never 324 | panic. This means, that if your database schema is known, then it's possible to write your 325 | application using only `from_value` with no fear of runtime panic. 326 | 327 | * `from_value_opt(Value) -> Option` - non-panicking, but less convenient conversion. 328 | 329 | This function is useful to probe conversion in cases, where source database schema 330 | is unknown. 331 | 332 | ```rust 333 | use mysql::*; 334 | use mysql::prelude::*; 335 | 336 | let via_test_protocol: u32 = from_value(Value::Bytes(b"65536".to_vec())); 337 | let via_bin_protocol: u32 = from_value(Value::UInt(65536)); 338 | assert_eq!(via_test_protocol, via_bin_protocol); 339 | 340 | let unknown_val = // ... 341 | 342 | // Maybe it is a float? 343 | let unknown_val = match from_value_opt::(unknown_val) { 344 | Ok(float) => { 345 | println!("A float value: {}", float); 346 | return Ok(()); 347 | } 348 | Err(FromValueError(unknown_val)) => unknown_val, 349 | }; 350 | 351 | // Or a string? 352 | let unknown_val = match from_value_opt::(unknown_val) { 353 | Ok(string) => { 354 | println!("A string value: {}", string); 355 | return Ok(()); 356 | } 357 | Err(FromValueError(unknown_val)) => unknown_val, 358 | }; 359 | 360 | // Screw this, I'll simply match on it 361 | match unknown_val { 362 | val @ Value::NULL => { 363 | println!("An empty value: {:?}", from_value::>(val)) 364 | }, 365 | val @ Value::Bytes(..) => { 366 | // It's non-utf8 bytes, since we already tried to convert it to String 367 | println!("Bytes: {:?}", from_value::>(val)) 368 | } 369 | val @ Value::Int(..) => { 370 | println!("A signed integer: {}", from_value::(val)) 371 | } 372 | val @ Value::UInt(..) => { 373 | println!("An unsigned integer: {}", from_value::(val)) 374 | } 375 | Value::Float(..) => unreachable!("already tried"), 376 | val @ Value::Double(..) => { 377 | println!("A double precision float value: {}", from_value::(val)) 378 | } 379 | val @ Value::Date(..) => { 380 | use time::PrimitiveDateTime; 381 | println!("A date value: {}", from_value::(val)) 382 | } 383 | val @ Value::Time(..) => { 384 | use std::time::Duration; 385 | println!("A time value: {:?}", from_value::(val)) 386 | } 387 | } 388 | ``` 389 | 390 | #### `Row` 391 | 392 | Internally `Row` is a vector of `Value`s, that also allows indexing by a column name/offset, 393 | and stores row metadata. Library offers conversion between `Row` and sequences of Rust types 394 | via `FromRow` trait described below. 395 | 396 | ##### `FromRow` trait 397 | 398 | This trait is reexported from **mysql_common** create. Please refer to its 399 | [crate docs][mysql_common docs] for the list of supported conversions. 400 | 401 | This conversion is based on the `FromValue` and so comes in two similar flavours: 402 | 403 | * `from_row(Row) -> T` - same as `from_value`, but for rows; 404 | * `from_row_opt(Row) -> Option` - same as `from_value_opt`, but for rows. 405 | 406 | [`Queryable`](#queryable) 407 | trait offers implicit conversion for rows of a query result, 408 | that is based on this trait. 409 | 410 | ```rust 411 | use mysql::*; 412 | use mysql::prelude::*; 413 | 414 | let mut conn = Conn::new(get_opts())?; 415 | 416 | // Single-column row can be converted to a singular value: 417 | let val: Option = conn.query_first("SELECT 'foo'")?; 418 | assert_eq!(val.unwrap(), "foo"); 419 | 420 | // Example of a multi-column row conversion to an inferred type: 421 | let row = conn.query_first("SELECT 255, 256")?; 422 | assert_eq!(row, Some((255u8, 256u16))); 423 | 424 | // The FromRow trait does not support to-tuple conversion for rows with more than 12 columns, 425 | // but you can do this by hand using row indexing or `Row::take` method: 426 | let row: Row = conn.exec_first("select 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12", ())?.unwrap(); 427 | for i in 0..row.len() { 428 | assert_eq!(row[i], Value::Int(i as i64)); 429 | } 430 | 431 | // Another way to handle wide rows is to use HList (requires `mysql_common/frunk` feature) 432 | use frunk::{HList, hlist, hlist_pat}; 433 | let query = "select 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"; 434 | type RowType = HList!(u8, u16, u32, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8); 435 | let first_three_columns = conn.query_map(query, |row: RowType| { 436 | // do something with the row (see the `frunk` crate documentation) 437 | let hlist_pat![c1, c2, c3, ...] = row; 438 | (c1, c2, c3) 439 | }); 440 | assert_eq!(first_three_columns.unwrap(), vec![(0_u8, 1_u16, 2_u32)]); 441 | 442 | // Some unknown row 443 | let row: Row = conn.query_first( 444 | // ... 445 | # "SELECT 255, Null", 446 | )?.unwrap(); 447 | 448 | for column in row.columns_ref() { 449 | // Cells in a row can be indexed by numeric index or by column name 450 | let column_value = &row[column.name_str().as_ref()]; 451 | 452 | println!( 453 | "Column {} of type {:?} with value {:?}", 454 | column.name_str(), 455 | column.column_type(), 456 | column_value, 457 | ); 458 | } 459 | ``` 460 | 461 | #### `Params` 462 | 463 | Represents parameters of a prepared statement, but this type won't appear directly in your code 464 | because binary protocol API will ask for `T: Into`, where `Into` is implemented: 465 | 466 | * for tuples of `Into` types up to arity 12; 467 | 468 | **Note:** singular tuple requires extra comma, e.g. `("foo",)`; 469 | 470 | * for `IntoIterator>` for cases, when your statement takes more 471 | than 12 parameters; 472 | * for named parameters representation (the value of the `params!` macro, described below). 473 | 474 | ```rust 475 | use mysql::*; 476 | use mysql::prelude::*; 477 | 478 | let mut conn = Conn::new(get_opts())?; 479 | 480 | // Singular tuple requires extra comma: 481 | let row: Option = conn.exec_first("SELECT ?", (0,))?; 482 | assert_eq!(row.unwrap(), 0); 483 | 484 | // More than 12 parameters: 485 | let row: Option = conn.exec_first( 486 | "SELECT CONVERT(? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ? + ?, UNSIGNED)", 487 | (0..16).collect::>(), 488 | )?; 489 | assert_eq!(row.unwrap(), 120); 490 | ``` 491 | 492 | **Note:** Please refer to the [**mysql_common** crate docs][mysql_common docs] for the list 493 | of types, that implements `Into`. 494 | 495 | ##### `Serialized`, `Deserialized` 496 | 497 | Wrapper structures for cases, when you need to provide a value for a JSON cell, 498 | or when you need to parse JSON cell as a struct. 499 | 500 | ```rust 501 | use mysql::*; 502 | use mysql::prelude::*; 503 | use serde::{Deserialize, Serialize}; 504 | 505 | /// Serializable structure. 506 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 507 | struct Example { 508 | foo: u32, 509 | } 510 | 511 | // Value::from for Serialized will emit json string. 512 | let value = Value::from(Serialized(Example { foo: 42 })); 513 | assert_eq!(value, Value::Bytes(br#"{"foo":42}"#.to_vec())); 514 | 515 | // from_value for Deserialized will parse json string. 516 | let structure: Deserialized = from_value(value); 517 | assert_eq!(structure, Deserialized(Example { foo: 42 })); 518 | ``` 519 | 520 | #### [`QueryResult`] 521 | 522 | It's an iterator over rows of a query result with support of multi-result sets. It's intended 523 | for cases when you need full control during result set iteration. For other cases 524 | [`Queryable`](#queryable) provides a set of methods that will immediately consume 525 | the first result set and drop everything else. 526 | 527 | This iterator is lazy so it won't read the result from server until you iterate over it. 528 | MySql protocol is strictly sequential, so `Conn` will be mutably borrowed until the result 529 | is fully consumed (please also look at [`QueryResult::iter`] docs). 530 | 531 | ```rust 532 | use mysql::*; 533 | use mysql::prelude::*; 534 | 535 | let mut conn = Conn::new(get_opts())?; 536 | 537 | // This query will emit two result sets. 538 | let mut result = conn.query_iter("SELECT 1, 2; SELECT 3, 3.14;")?; 539 | 540 | let mut sets = 0; 541 | while let Some(result_set) = result.iter() { 542 | sets += 1; 543 | 544 | println!("Result set columns: {:?}", result_set.columns()); 545 | println!( 546 | "Result set meta: {}, {:?}, {} {}", 547 | result_set.affected_rows(), 548 | result_set.last_insert_id(), 549 | result_set.warnings(), 550 | result_set.info_str(), 551 | ); 552 | 553 | for row in result_set { 554 | match sets { 555 | 1 => { 556 | // First result set will contain two numbers. 557 | assert_eq!((1_u8, 2_u8), from_row(row?)); 558 | } 559 | 2 => { 560 | // Second result set will contain a number and a float. 561 | assert_eq!((3_u8, 3.14), from_row(row?)); 562 | } 563 | _ => unreachable!(), 564 | } 565 | } 566 | } 567 | 568 | assert_eq!(sets, 2); 569 | ``` 570 | 571 | ### Text protocol 572 | 573 | MySql text protocol is implemented in the set of `Queryable::query*` methods. It's useful when your 574 | query doesn't have parameters. 575 | 576 | **Note:** All values of a text protocol result set will be encoded as strings by the server, 577 | so `from_value` conversion may lead to additional parsing costs. 578 | 579 | Examples: 580 | 581 | ```rust 582 | let pool = Pool::new(get_opts())?; 583 | let val = pool.get_conn()?.query_first("SELECT POW(2, 16)")?; 584 | 585 | // Text protocol returns bytes even though the result of POW 586 | // is actually a floating point number. 587 | assert_eq!(val, Some(Value::Bytes("65536".as_bytes().to_vec()))); 588 | ``` 589 | 590 | #### The `TextQuery` trait. 591 | 592 | The `TextQuery` trait covers the set of `Queryable::query*` methods from the perspective 593 | of a query, i.e. `TextQuery` is something, that can be performed if suitable connection 594 | is given. Suitable connections are: 595 | 596 | * `&Pool` 597 | * `Conn` 598 | * `PooledConn` 599 | * `&mut Conn` 600 | * `&mut PooledConn` 601 | * `&mut Transaction` 602 | 603 | The unique characteristic of this trait, is that you can give away the connection 604 | and thus produce `QueryResult` that satisfies `'static`: 605 | 606 | ```rust 607 | use mysql::*; 608 | use mysql::prelude::*; 609 | 610 | fn iter(pool: &Pool) -> Result>> { 611 | let result = "SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3".run(pool)?; 612 | Ok(result.map(|row| row.map(from_row))) 613 | } 614 | 615 | let pool = Pool::new(get_opts())?; 616 | 617 | let it = iter(&pool)?; 618 | 619 | assert_eq!(it.collect::>>()?, vec![1, 2, 3]); 620 | ``` 621 | 622 | ### Binary protocol and prepared statements. 623 | 624 | MySql binary protocol is implemented in `prep`, `close` and the set of `exec*` methods, 625 | defined on the [`Queryable`](#queryable) trait. Prepared statements is the only way to 626 | pass rust value to the MySql server. MySql uses `?` symbol as a parameter placeholder 627 | and it's only possible to use parameters where a single MySql value is expected. 628 | For example: 629 | 630 | ```rust 631 | let pool = Pool::new(get_opts())?; 632 | let val = pool.get_conn()?.exec_first("SELECT POW(?, ?)", (2, 16))?; 633 | 634 | assert_eq!(val, Some(Value::Double(65536.0))); 635 | ``` 636 | 637 | #### Statements 638 | 639 | In MySql each prepared statement belongs to a particular connection and can't be executed 640 | on another connection. Trying to do so will lead to an error. The driver won't tie statement 641 | to its connection in any way, but one can look on to the connection id, contained 642 | in the `Statement` structure. 643 | 644 | ```rust 645 | let pool = Pool::new(get_opts())?; 646 | 647 | let mut conn_1 = pool.get_conn()?; 648 | let mut conn_2 = pool.get_conn()?; 649 | 650 | let stmt_1 = conn_1.prep("SELECT ?")?; 651 | 652 | // stmt_1 is for the conn_1, .. 653 | assert!(stmt_1.connection_id() == conn_1.connection_id()); 654 | assert!(stmt_1.connection_id() != conn_2.connection_id()); 655 | 656 | // .. so stmt_1 will execute only on conn_1 657 | assert!(conn_1.exec_drop(&stmt_1, ("foo",)).is_ok()); 658 | assert!(conn_2.exec_drop(&stmt_1, ("foo",)).is_err()); 659 | ``` 660 | 661 | #### Statement cache 662 | 663 | ##### Note 664 | 665 | Statement cache only works for: 666 | 1. for raw [`Conn`] 667 | 2. for [`PooledConn`]: 668 | * within its lifetime if [`PoolOpts::reset_connection`] is `true` 669 | * within the lifetime of a wrapped [`Conn`] if [`PoolOpts::reset_connection`] is `false` 670 | 671 | ##### Description 672 | 673 | `Conn` will manage the cache of prepared statements on the client side, so subsequent calls 674 | to prepare with the same statement won't lead to a client-server roundtrip. Cache size 675 | for each connection is determined by the `stmt_cache_size` field of the `Opts` structure. 676 | Statements, that are out of this boundary will be closed in LRU order. 677 | 678 | Statement cache is completely disabled if `stmt_cache_size` is zero. 679 | 680 | **Caveats:** 681 | 682 | * disabled statement cache means, that you have to close statements yourself using 683 | `Conn::close`, or they'll exhaust server limits/resources; 684 | 685 | * you should be aware of the [`max_prepared_stmt_count`][max_prepared_stmt_count] 686 | option of the MySql server. If the number of active connections times the value 687 | of `stmt_cache_size` is greater, than you could receive an error while preparing 688 | another statement. 689 | 690 | #### Named parameters 691 | 692 | MySql itself doesn't have named parameters support, so it's implemented on the client side. 693 | One should use `:name` as a placeholder syntax for a named parameter. Named parameters uses 694 | the following naming convention: 695 | 696 | * parameter name must start with either `_` or `a..z` 697 | * parameter name may continue with `_`, `a..z` and `0..9` 698 | 699 | Named parameters may be repeated within the statement, e.g `SELECT :foo, :foo` will require 700 | a single named parameter `foo` that will be repeated on the corresponding positions during 701 | statement execution. 702 | 703 | One should use the `params!` macro to build parameters for execution. 704 | 705 | **Note:** Positional and named parameters can't be mixed within the single statement. 706 | 707 | Examples: 708 | 709 | ```rust 710 | let pool = Pool::new(get_opts())?; 711 | 712 | let mut conn = pool.get_conn()?; 713 | let stmt = conn.prep("SELECT :foo, :bar, :foo")?; 714 | 715 | let foo = 42; 716 | 717 | let val_13 = conn.exec_first(&stmt, params! { "foo" => 13, "bar" => foo })?.unwrap(); 718 | // Short syntax is available when param name is the same as variable name: 719 | let val_42 = conn.exec_first(&stmt, params! { foo, "bar" => 13 })?.unwrap(); 720 | 721 | assert_eq!((foo, 13, foo), val_42); 722 | assert_eq!((13, foo, 13), val_13); 723 | ``` 724 | 725 | #### Buffer pool 726 | 727 | Crate uses the global lock-free buffer pool for the purpose of IO and data serialization/deserialization, 728 | that helps to avoid allocations for basic scenarios. You can control its characteristics using 729 | the following environment variables: 730 | 731 | * `RUST_MYSQL_BUFFER_POOL_CAP` (defaults to 128) – controls the pool capacity. Dropped buffer will 732 | be immediately deallocated if the pool is full. Set it to `0` to disable the pool at runtime. 733 | 734 | * `RUST_MYSQL_BUFFER_SIZE_CAP` (defaults to 4MiB) – controls the maximum capacity of a buffer 735 | stored in the pool. Capacity of a dropped buffer will be shrunk to this value when buffer 736 | is returned to the pool. 737 | 738 | To completely disable the pool (say you are using jemalloc) please remove the `buffer-pool` feature 739 | from the set of default crate features (see the [Crate Features](#crate-features) section). 740 | 741 | #### `BinQuery` and `BatchQuery` traits. 742 | 743 | `BinQuery` and `BatchQuery` traits covers the set of `Queryable::exec*` methods from 744 | the perspective of a query, i.e. `BinQuery` is something, that can be performed if suitable 745 | connection is given (see [`TextQuery`](#the-textquery-trait) section for the list 746 | of suitable connections). 747 | 748 | As with the [`TextQuery`](#the-textquery-trait) you can give away the connection and acquire 749 | `QueryResult` that satisfies `'static`. 750 | 751 | `BinQuery` is for prepared statements, and prepared statements requires a set of parameters, 752 | so `BinQuery` is implemented for `QueryWithParams` structure, that can be acquired, using 753 | `WithParams` trait. 754 | 755 | Example: 756 | 757 | ```rust 758 | use mysql::*; 759 | use mysql::prelude::*; 760 | 761 | let pool = Pool::new(get_opts())?; 762 | 763 | let result: Option<(u8, u8, u8)> = "SELECT ?, ?, ?" 764 | .with((1, 2, 3)) // <- WithParams::with will construct an instance of QueryWithParams 765 | .first(&pool)?; // <- QueryWithParams is executed on the given pool 766 | 767 | assert_eq!(result.unwrap(), (1, 2, 3)); 768 | ``` 769 | 770 | The `BatchQuery` trait is a helper for batch statement execution. It's implemented for 771 | `QueryWithParams` where parameters is an iterator over parameters: 772 | 773 | ```rust 774 | use mysql::*; 775 | use mysql::prelude::*; 776 | 777 | let pool = Pool::new(get_opts())?; 778 | let mut conn = pool.get_conn()?; 779 | 780 | "CREATE TEMPORARY TABLE batch (x INT)".run(&mut conn)?; 781 | "INSERT INTO batch (x) VALUES (?)" 782 | .with((0..3).map(|x| (x,))) // <- QueryWithParams constructed with an iterator 783 | .batch(&mut conn)?; // <- batch execution is preformed here 784 | 785 | let result: Vec = "SELECT x FROM batch".fetch(conn)?; 786 | 787 | assert_eq!(result, vec![0, 1, 2]); 788 | ``` 789 | 790 | #### `Queryable` 791 | 792 | The `Queryable` trait defines common methods for `Conn`, `PooledConn` and `Transaction`. 793 | The set of basic methods consts of: 794 | 795 | * `query_iter` - basic methods to execute text query and get `QueryResult`; 796 | * `prep` - basic method to prepare a statement; 797 | * `exec_iter` - basic method to execute statement and get `QueryResult`; 798 | * `close` - basic method to close the statement; 799 | 800 | The trait also defines the set of helper methods, that is based on basic methods. 801 | These methods will consume only the first result set, other result sets will be dropped: 802 | 803 | * `{query|exec}` - to collect the result into a `Vec`; 804 | * `{query|exec}_first` - to get the first `T: FromRow`, if any; 805 | * `{query|exec}_map` - to map each `T: FromRow` to some `U`; 806 | * `{query|exec}_fold` - to fold the set of `T: FromRow` to a single value; 807 | * `{query|exec}_drop` - to immediately drop the result. 808 | 809 | The trait also defines the `exec_batch` function, which is a helper for batch statement 810 | execution. 811 | 812 | ### SSL Support 813 | 814 | SSL support comes in two flavors: 815 | 816 | 1. Based on the `native-tls` crate – native TLS backend. 817 | 818 | This uses the native OS SSL/TLS provider. Enabled by the **rustls-tls** feature. 819 | 820 | 2. Based on the `rustls` – TLS backend written in Rust. You have three options here: 821 | 822 | 1. **rustls-tls** feature enables `rustls` backend with `aws-lc-rs` crypto provider 823 | 2. **rustls-tls-ring** feature enables `rustls` backend with `ring` crypto provider 824 | 3. **rustls** feature enables `rustls` backend without crypto provider — you have to 825 | install your own provider to avoid "no process-level CryptoProvider available" error 826 | (see relevant section of the [`rustls` crate docs](https://docs.rs/rustls)) 827 | 828 | Please also note a few things about **rustls**: 829 | 830 | * it will fail if you'll try to connect to the server by its IP address, hostname is required; 831 | * it, most likely, won't work on windows, at least with default server certs, generated by the 832 | MySql installer. 833 | 834 | [crate docs]: https://docs.rs/mysql 835 | [mysql_common docs]: https://docs.rs/mysql_common 836 | [max_prepared_stmt_count]: https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_prepared_stmt_count 837 | [derive_docs]: https://docs.rs/mysql_common/latest/mysql_common/#derive-macros 838 | [common_features]: https://docs.rs/mysql_common/latest/mysql_common/#crate-features 839 | 840 | ## Changelog 841 | 842 | Available [here](https://github.com/blackbeam/rust-mysql-simple/releases) 843 | 844 | ## License 845 | 846 | Licensed under either of 847 | 848 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) 849 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 850 | 851 | at your option. 852 | 853 | ### Contribution 854 | 855 | Unless you explicitly state otherwise, any contribution intentionally 856 | submitted for inclusion in the work by you, as defined in the Apache-2.0 857 | license, shall be dual licensed as above, without any additional terms or 858 | conditions. 859 | -------------------------------------------------------------------------------- /README.tpl: -------------------------------------------------------------------------------- 1 | [![Gitter](https://badges.gitter.im/rust-mysql/community.svg)](https://gitter.im/rust-mysql/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/mysql.svg)](https://crates.io/crates/mysql) 4 | [![Build Status](https://dev.azure.com/aikorsky/mysql%20Rust/_apis/build/status/blackbeam%2Erust%2Dmysql%2Dsimple)](https://dev.azure.com/aikorsky/mysql%20Rust/_build/latest?definitionId=1) 5 | 6 | # {{crate}} 7 | 8 | {{readme}} 9 | 10 | ## Changelog 11 | 12 | Available [here](https://github.com/blackbeam/rust-mysql-simple/releases) 13 | 14 | ## License 15 | 16 | Licensed under either of 17 | 18 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) 19 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 20 | 21 | at your option. 22 | 23 | ### Contribution 24 | 25 | Unless you explicitly state otherwise, any contribution intentionally 26 | submitted for inclusion in the work by you, as defined in the Apache-2.0 27 | license, shall be dual licensed as above, without any additional terms or 28 | conditions. -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - ci-* 4 | 5 | jobs: 6 | - job: "TestBasicLinux" 7 | pool: 8 | vmImage: "ubuntu-latest" 9 | strategy: 10 | maxParallel: 10 11 | matrix: 12 | stable: 13 | RUST_TOOLCHAIN: stable 14 | beta: 15 | RUST_TOOLCHAIN: beta 16 | nightly: 17 | RUST_TOOLCHAIN: nightly 18 | steps: 19 | - bash: | 20 | sudo apt-get update 21 | sudo apt-get -y install pkg-config libssl-dev build-essential 22 | displayName: Install Dependencies 23 | - bash: | 24 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $(RUST_TOOLCHAIN) 25 | echo '##vso[task.setvariable variable=toolchain;isOutput=true]$(RUST_TOOLCHAIN)' 26 | displayName: Install Rust 27 | name: installRust 28 | - bash: | 29 | cargo fmt -- --check 30 | condition: and(succeeded(), eq(variables['installRust.toolchain'], 'stable')) 31 | displayName: cargo fmt 32 | - bash: | 33 | cargo clippy -- -Dclippy::dbg_macro -Dclippy::todo 34 | condition: and(succeeded(), eq(variables['installRust.toolchain'], 'stable')) 35 | displayName: lint 36 | - bash: | 37 | cargo check 38 | cargo check --no-default-features --features default-rust 39 | cargo check --no-default-features --features minimal 40 | displayName: Run check 41 | 42 | - job: "TestBasicMacOs" 43 | pool: 44 | vmImage: "macOS-13" 45 | strategy: 46 | maxParallel: 10 47 | matrix: 48 | stable: 49 | RUST_TOOLCHAIN: stable 50 | steps: 51 | - bash: | 52 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUST_TOOLCHAIN 53 | displayName: Install rust (MacOs) 54 | - bash: | 55 | cargo check 56 | cargo check --no-default-features --features default-rust 57 | cargo check --no-default-features --features minimal 58 | displayName: Run check 59 | 60 | - job: "TestBasicWindows" 61 | pool: 62 | vmImage: "windows-2019" 63 | strategy: 64 | maxParallel: 10 65 | matrix: 66 | stable: 67 | RUST_TOOLCHAIN: stable 68 | steps: 69 | - script: | 70 | choco install 7zip 71 | mkdir C:\mysql 72 | CD /D C:\mysql 73 | curl -fsS --retry 3 --retry-connrefused -o mysql.msi https://cdn.mysql.com//Downloads/MySQLInstaller/mysql-installer-community-8.0.42.0.msi 74 | msiexec /q /log install.txt /i mysql.msi datadir=C:\mysql installdir=C:\mysql 75 | call "C:\Program Files (x86)\MySQL\MySQL Installer for Windows\MySQLInstallerConsole.exe" community install server;8.0.42;x64:*:port=3306;enable_named_pipe=true;rootpasswd=password;servicename=MySQL -silent 76 | netsh advfirewall firewall add rule name="Allow mysql" dir=in action=allow edge=yes remoteip=any protocol=TCP localport=80,8080,3306 77 | net stop MySQL 78 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld" --remove 79 | echo [mysqld] >> C:\my.cnf 80 | echo enable-named-pipe >> C:\my.cnf 81 | echo socket=MYSQL >> C:\my.cnf 82 | echo named_pipe_full_access_group=*everyone* >> C:\my.cnf 83 | echo datadir=C:\\ProgramData\\MySQL\\MySQL Server 8.0\\Data\\ >> C:\my.cnf 84 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld" --install MySQL --defaults-file=C:\my.cnf 85 | net start MySQL 86 | cat C:\my.cnf 87 | cat "C:\ProgramData\MySQL\MySQL Server 8.0\Data\*err" 88 | cat "C:\ProgramData\MySQL\MySQL Server 8.0\Data\*log" 89 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET GLOBAL max_allowed_packet = 36700160;" -uroot -ppassword 90 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = WARN;" -uroot -ppassword 91 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = ON;" -uroot -ppassword 92 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;" -uroot -ppassword 93 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET @@GLOBAL.GTID_MODE = ON_PERMISSIVE;" -uroot -ppassword 94 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET @@GLOBAL.GTID_MODE = ON;" -uroot -ppassword 95 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SET GLOBAL local_infile=1;" -uroot -ppassword 96 | "C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "SHOW VARIABLES;" -uroot -ppassword 97 | displayName: Install MySql 98 | - bash: | 99 | rustup install $RUST_TOOLCHAIN 100 | displayName: Install Rust (Windows) 101 | - bash: | 102 | SSL=false COMPRESS=false cargo test 103 | SSL=true COMPRESS=false cargo test --features native-tls 104 | SSL=false COMPRESS=true cargo test 105 | SSL=true COMPRESS=true cargo test --features native-tls 106 | 107 | SSL=true COMPRESS=false cargo test --no-default-features --features rustls-tls,minimal-rust,time,frunk,binlog 108 | SSL=true COMPRESS=true cargo test --no-default-features --features rustls-tls-ring,minimal-rust,time,frunk,binlog 109 | 110 | SSL=false COMPRESS=true cargo test --no-default-features --features minimal,time,frunk,binlog 111 | SSL=false COMPRESS=false cargo test --no-default-features --features minimal,time,frunk,binlog 112 | env: 113 | RUST_BACKTRACE: 1 114 | DATABASE_URL: mysql://root:password@localhost/mysql 115 | displayName: Run tests 116 | 117 | - job: "TestTiDB" 118 | pool: 119 | vmImage: "ubuntu-latest" 120 | strategy: 121 | matrix: 122 | v7.5.1: 123 | DB_VERSION: "v7.5.1" 124 | v6.5.8: 125 | DB_VERSION: "v6.5.8" 126 | v5.3.4: 127 | DB_VERSION: "v5.3.4" 128 | v5.0.6: 129 | DB_VERSION: "v5.0.6" 130 | steps: 131 | - bash: | 132 | curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh 133 | source ~/.profile 134 | tiup playground $(DB_VERSION) --db 1 --pd 1 --kv 1 & 135 | while ! nc -W 1 localhost 4000 | grep -q -P '.+'; do sleep 1; done 136 | displayName: Install and run TiDB 137 | - bash: cargo test should_reuse_connections -- --nocapture 138 | displayName: Run tests 139 | env: 140 | RUST_BACKTRACE: 1 141 | DATABASE_URL: mysql://root@127.0.0.1:4000/mysql 142 | 143 | - job: "TestMySql" 144 | pool: 145 | vmImage: "ubuntu-latest" 146 | strategy: 147 | maxParallel: 10 148 | matrix: 149 | v91: 150 | DB_VERSION: "9.1" 151 | v90: 152 | DB_VERSION: "9.0" 153 | v84: 154 | DB_VERSION: "8.4" 155 | v80: 156 | DB_VERSION: "8.0-debian" 157 | v57: 158 | DB_VERSION: "5.7-debian" 159 | v56: 160 | DB_VERSION: "5.6" 161 | steps: 162 | - bash: | 163 | sudo apt-get update 164 | sudo apt-get install docker.io netcat grep 165 | sudo systemctl unmask docker 166 | sudo systemctl start docker 167 | docker --version 168 | displayName: Install docker 169 | - bash: | 170 | if [[ "5.6" == "$(DB_VERSION)" ]]; then ARG="--secure-auth=OFF"; fi 171 | docker run -d --name container -v `pwd`:/root -p 3307:3306 -e MYSQL_ROOT_PASSWORD=password mysql:$(DB_VERSION) --max-allowed-packet=36700160 --local-infile --log-bin=mysql-bin --log-slave-updates --gtid_mode=ON --enforce_gtid_consistency=ON --server-id=1 $ARG 172 | while ! nc -W 1 localhost 3307 | grep -q -P '.+'; do sleep 1; done 173 | displayName: Run MySql in Docker 174 | - bash: | 175 | docker exec container bash -l -c "mysql -uroot -ppassword -e \"SET old_passwords = 1; GRANT ALL PRIVILEGES ON *.* TO 'root2'@'%' IDENTIFIED WITH mysql_old_password AS 'password'; SET PASSWORD FOR 'root2'@'%' = OLD_PASSWORD('password')\""; 176 | docker exec container bash -l -c "echo 'deb [trusted=yes] http://archive.debian.org/debian/ stretch main non-free contrib' > /etc/apt/sources.list" 177 | docker exec container bash -l -c "echo 'deb-src [trusted=yes] http://archive.debian.org/debian/ stretch main non-free contrib ' >> /etc/apt/sources.list" 178 | docker exec container bash -l -c "echo 'deb [trusted=yes] http://archive.debian.org/debian-security/ stretch/updates main non-free contrib' >> /etc/apt/sources.list" 179 | docker exec container bash -l -c "echo 'deb [trusted=yes] http://repo.mysql.com/apt/debian/ stretch mysql-5.6' > /etc/apt/sources.list.d/mysql.list" 180 | condition: eq(variables['DB_VERSION'], '5.6') 181 | - bash: | 182 | docker exec container bash -l -c "apt-get --allow-unauthenticated -y update" 183 | docker exec container bash -l -c "apt-get install -y curl clang libssl-dev pkg-config build-essential" 184 | docker exec container bash -l -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable" 185 | displayName: Install Rust in docker (Debian) 186 | condition: or(eq(variables['DB_VERSION'], '5.6'), eq(variables['DB_VERSION'], '5.7-debian'), eq(variables['DB_VERSION'], '8.0-debian')) 187 | - bash: | 188 | docker exec container bash -l -c "microdnf install dnf" 189 | docker exec container bash -l -c "dnf group install \"Development Tools\"" 190 | docker exec container bash -l -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable" 191 | displayName: Install Rust in docker (RedHat) 192 | condition: not(or(eq(variables['DB_VERSION'], '5.6'), eq(variables['DB_VERSION'], '5.7-debian'), eq(variables['DB_VERSION'], '8.0-debian'))) 193 | - bash: | 194 | if [[ "5.6" != "$(DB_VERSION)" ]]; then SSL=true; else DATABASE_URL="mysql://root2:password@localhost/mysql?secure_auth=false"; fi 195 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL cargo test" 196 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL COMPRESS=true cargo test" 197 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=$SSL cargo test --features native-tls" 198 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=$SSL COMPRESS=true cargo test --features native-tls" 199 | 200 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true COMPRESS=false cargo test --no-default-features --features rustls-tls,minimal-rust,time,frunk,binlog" 201 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true COMPRESS=true cargo test --no-default-features --features rustls-tls-ring,minimal-rust,time,frunk,binlog" 202 | 203 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=false COMPRESS=true cargo test --no-default-features --features minimal,time,frunk,binlog" 204 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=false COMPRESS=false cargo test --no-default-features --features minimal,time,frunk,binlog" 205 | env: 206 | RUST_BACKTRACE: 1 207 | DATABASE_URL: mysql://root:password@localhost/mysql 208 | displayName: Run tests in Docker 209 | 210 | - job: "TestMariaDb" 211 | pool: 212 | vmImage: "ubuntu-latest" 213 | strategy: 214 | maxParallel: 10 215 | matrix: 216 | verylatest: 217 | CONTAINER: "quay.io/mariadb-foundation/mariadb-devel:verylatest" 218 | latest: 219 | CONTAINER: "quay.io/mariadb-foundation/mariadb-devel:latest" 220 | lts: 221 | CONTAINER: "mariadb:lts" 222 | v1106: 223 | CONTAINER: "mariadb:11.6" 224 | v1105: 225 | CONTAINER: "mariadb:11.5" 226 | v1104: 227 | CONTAINER: "mariadb:11.4" 228 | v1103: 229 | CONTAINER: "mariadb:11.3" 230 | v1011: 231 | CONTAINER: "mariadb:10.11.10" 232 | steps: 233 | - bash: | 234 | sudo apt-get update 235 | sudo apt-get install docker.io 236 | sudo systemctl unmask docker 237 | sudo systemctl start docker 238 | docker --version 239 | displayName: Install docker 240 | - bash: | 241 | docker run --rm -d \ 242 | --name container \ 243 | -v `pwd`:/root \ 244 | -p 3307:3306 \ 245 | -e MARIADB_ROOT_PASSWORD=password \ 246 | $(CONTAINER) \ 247 | --max-allowed-packet=36700160 \ 248 | --local-infile \ 249 | --performance-schema=on \ 250 | --log-bin=mysql-bin --gtid-domain-id=1 \ 251 | --server-id=1 \ 252 | --ssl \ 253 | --ssl-ca=/root/tests/ca.crt \ 254 | --ssl-cert=/root/tests/server.crt \ 255 | --ssl-key=/root/tests/server-key.pem \ 256 | --secure-auth=OFF \ 257 | --plugin-load-add=auth_ed25519 & 258 | while ! docker exec container healthcheck.sh --connect --innodb_initialized ; do sleep 1; echo waiting; done 259 | docker logs container 260 | docker exec container bash -l -c "mariadb -uroot -ppassword -e 'SHOW VARIABLES LIKE \"%ssl%\"'" 261 | docker exec container bash -l -c "ls -la /root/tests" 262 | displayName: Run MariaDb in Docker 263 | - bash: | 264 | docker exec container bash -l -c "apt-get update" 265 | docker exec container bash -l -c "apt-get install -y curl clang libssl-dev pkg-config" 266 | docker exec container bash -l -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable" 267 | displayName: Install Rust in docker 268 | - bash: | 269 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL cargo test --features client_ed25519" 270 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL COMPRESS=true cargo test --features client_ed25519" 271 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true cargo test --features native-tls,client_ed25519" 272 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true COMPRESS=true cargo test --features native-tls,client_ed25519" 273 | 274 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true COMPRESS=false cargo test --no-default-features --features rustls-tls,minimal-rust,time,frunk,binlog,client_ed25519" 275 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=true COMPRESS=true cargo test --no-default-features --features rustls-tls-ring,minimal-rust,time,frunk,binlog,client_ed25519" 276 | 277 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=false COMPRESS=true cargo test --no-default-features --features minimal,time,frunk,binlog,client_ed25519" 278 | docker exec container bash -l -c "cd \$HOME && DATABASE_URL=$DATABASE_URL SSL=false COMPRESS=false cargo test --no-default-features --features minimal,time,frunk,binlog,client_ed25519" 279 | env: 280 | RUST_BACKTRACE: 1 281 | DATABASE_URL: mysql://root:password@localhost/mysql 282 | displayName: Run tests in Docker 283 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-common contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use std::env; 10 | 11 | fn main() { 12 | let names = ["CARGO_CFG_TARGET_OS", "CARGO_CFG_TARGET_ARCH"]; 13 | for name in &names { 14 | let value = env::var(name) 15 | .unwrap_or_else(|_| panic!("Could not get the environment variable {}", name)); 16 | println!("cargo:rustc-env={}={}", name, value); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/buffer_pool/disabled.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(feature = "buffer-pool"))] 2 | 3 | use std::ops::Deref; 4 | 5 | #[derive(Debug)] 6 | #[repr(transparent)] 7 | pub struct Buffer(Vec); 8 | 9 | impl AsMut> for Buffer { 10 | fn as_mut(&mut self) -> &mut Vec { 11 | &mut self.0 12 | } 13 | } 14 | 15 | impl Deref for Buffer { 16 | type Target = [u8]; 17 | 18 | fn deref(&self) -> &Self::Target { 19 | self.0.deref() 20 | } 21 | } 22 | 23 | pub const fn get_buffer() -> Buffer { 24 | Buffer(Vec::new()) 25 | } 26 | -------------------------------------------------------------------------------- /src/buffer_pool/enabled.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "buffer-pool")] 2 | 3 | use crossbeam_queue::ArrayQueue; 4 | 5 | use std::{ 6 | mem::take, 7 | ops::Deref, 8 | sync::{Arc, OnceLock}, 9 | }; 10 | 11 | const DEFAULT_MYSQL_BUFFER_POOL_CAP: usize = 128; 12 | const DEFAULT_MYSQL_BUFFER_SIZE_CAP: usize = 4 * 1024 * 1024; 13 | 14 | #[inline(always)] 15 | pub fn get_buffer() -> Buffer { 16 | static BUFFER_POOL: OnceLock> = OnceLock::new(); 17 | BUFFER_POOL.get_or_init(Default::default).get() 18 | } 19 | 20 | #[derive(Debug)] 21 | struct Inner { 22 | buffer_cap: usize, 23 | pool: ArrayQueue>, 24 | } 25 | 26 | impl Inner { 27 | fn get(self: &Arc) -> Buffer { 28 | let mut buf = self.pool.pop().unwrap_or_default(); 29 | 30 | // SAFETY: 31 | // 1. OK – 0 is always within capacity 32 | // 2. OK - nothing to initialize 33 | unsafe { buf.set_len(0) } 34 | 35 | Buffer(buf, Some(self.clone())) 36 | } 37 | 38 | fn put(&self, mut buf: Vec) { 39 | buf.shrink_to(self.buffer_cap); 40 | let _ = self.pool.push(buf); 41 | } 42 | } 43 | 44 | /// Smart pointer to a buffer pool. 45 | #[derive(Debug, Clone)] 46 | pub struct BufferPool(Option>); 47 | 48 | impl BufferPool { 49 | pub fn new() -> Self { 50 | let pool_cap = std::env::var("RUST_MYSQL_BUFFER_POOL_CAP") 51 | .ok() 52 | .and_then(|x| x.parse().ok()) 53 | .unwrap_or(DEFAULT_MYSQL_BUFFER_POOL_CAP); 54 | 55 | let buffer_cap = std::env::var("RUST_MYSQL_BUFFER_SIZE_CAP") 56 | .ok() 57 | .and_then(|x| x.parse().ok()) 58 | .unwrap_or(DEFAULT_MYSQL_BUFFER_SIZE_CAP); 59 | 60 | Self((pool_cap > 0).then(|| { 61 | Arc::new(Inner { 62 | buffer_cap, 63 | pool: ArrayQueue::new(pool_cap), 64 | }) 65 | })) 66 | } 67 | 68 | pub fn get(self: &Arc) -> Buffer { 69 | match self.0 { 70 | Some(ref inner) => inner.get(), 71 | None => Buffer(Vec::new(), None), 72 | } 73 | } 74 | } 75 | 76 | impl Default for BufferPool { 77 | fn default() -> Self { 78 | Self::new() 79 | } 80 | } 81 | 82 | #[derive(Debug)] 83 | pub struct Buffer(Vec, Option>); 84 | 85 | impl AsMut> for Buffer { 86 | fn as_mut(&mut self) -> &mut Vec { 87 | &mut self.0 88 | } 89 | } 90 | 91 | impl Deref for Buffer { 92 | type Target = [u8]; 93 | 94 | fn deref(&self) -> &Self::Target { 95 | self.0.deref() 96 | } 97 | } 98 | 99 | impl Drop for Buffer { 100 | fn drop(&mut self) { 101 | if let Some(ref inner) = self.1 { 102 | inner.put(take(&mut self.0)); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/buffer_pool/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Anatoly Ikorsky 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | mod disabled; 10 | mod enabled; 11 | 12 | #[cfg(feature = "buffer-pool")] 13 | pub use enabled::{get_buffer, Buffer}; 14 | 15 | #[cfg(not(feature = "buffer-pool"))] 16 | pub use disabled::{get_buffer, Buffer}; 17 | -------------------------------------------------------------------------------- /src/conn/binlog_stream.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Anatoly Ikorsky 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use mysql_common::{ 10 | binlog::{ 11 | consts::BinlogVersion::Version4, 12 | events::{Event, TableMapEvent}, 13 | EventStreamReader, 14 | }, 15 | io::ParseBuf, 16 | packets::{ErrPacket, NetworkStreamTerminator, OkPacketDeserializer}, 17 | }; 18 | 19 | use crate::Conn; 20 | 21 | /// Binlog event stream. 22 | /// 23 | /// Stream initialization is lazy, i.e. binlog won't be requested until this stream is polled. 24 | #[cfg_attr(docsrs, doc(cfg(feature = "binlog")))] 25 | pub struct BinlogStream { 26 | conn: Option, 27 | esr: EventStreamReader, 28 | } 29 | 30 | impl BinlogStream { 31 | /// `conn` is a `Conn` with `request_binlog` executed on it. 32 | pub(super) fn new(conn: Conn) -> Self { 33 | BinlogStream { 34 | conn: Some(conn), 35 | esr: EventStreamReader::new(Version4), 36 | } 37 | } 38 | 39 | /// Returns a table map event for the given table id. 40 | pub fn get_tme(&self, table_id: u64) -> Option<&TableMapEvent<'static>> { 41 | self.esr.get_tme(table_id) 42 | } 43 | } 44 | 45 | impl Iterator for BinlogStream { 46 | type Item = crate::Result; 47 | 48 | fn next(&mut self) -> Option { 49 | let conn = self.conn.as_mut()?; 50 | 51 | let packet = match conn.read_packet() { 52 | Ok(packet) => packet, 53 | Err(err) => { 54 | self.conn = None; 55 | return Some(Err(err)); 56 | } 57 | }; 58 | 59 | let first_byte = packet.first().copied(); 60 | 61 | if first_byte == Some(255) { 62 | if let Ok(ErrPacket::Error(err)) = ParseBuf(&packet).parse(conn.0.capability_flags) { 63 | self.conn = None; 64 | return Some(Err(crate::Error::MySqlError(From::from(err)))); 65 | } 66 | } 67 | 68 | if first_byte == Some(254) 69 | && packet.len() < 8 70 | && ParseBuf(&packet) 71 | .parse::>(conn.0.capability_flags) 72 | .is_ok() 73 | { 74 | self.conn = None; 75 | return None; 76 | } 77 | 78 | if first_byte == Some(0) { 79 | let event_data = &packet[1..]; 80 | match self.esr.read(event_data) { 81 | Ok(event) => Some(Ok(event?)), 82 | Err(err) => Some(Err(err.into())), 83 | } 84 | } else { 85 | self.conn = None; 86 | Some(Err(crate::error::DriverError::UnexpectedPacket.into())) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/conn/local_infile.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use std::{ 10 | fmt, io, 11 | sync::{Arc, Mutex}, 12 | }; 13 | 14 | use crate::Conn; 15 | 16 | pub(crate) type LocalInfileInner = 17 | Arc FnMut(&'a [u8], &'a mut LocalInfile<'_>) -> io::Result<()> + Send>>; 18 | 19 | /// Callback to handle requests for local files. 20 | /// Consult [Mysql documentation](https://dev.mysql.com/doc/refman/5.7/en/load-data.html) for the 21 | /// format of local infile data. 22 | /// 23 | /// # Support 24 | /// 25 | /// Note that older versions of Mysql server may not support this functionality. 26 | /// 27 | /// ```rust 28 | /// # mysql::doctest_wrapper!(__result, { 29 | /// use mysql::*; 30 | /// use mysql::prelude::*; 31 | /// 32 | /// use std::io::Write; 33 | /// 34 | /// let pool = Pool::new(get_opts())?; 35 | /// let mut conn = pool.get_conn().unwrap(); 36 | /// 37 | /// conn.query_drop("CREATE TEMPORARY TABLE mysql.tbl(a TEXT)").unwrap(); 38 | /// conn.set_local_infile_handler(Some( 39 | /// LocalInfileHandler::new(|file_name, writer| { 40 | /// writer.write_all(b"row1: file name is ")?; 41 | /// writer.write_all(file_name)?; 42 | /// writer.write_all(b"\n")?; 43 | /// 44 | /// writer.write_all(b"row2: foobar\n") 45 | /// }) 46 | /// )); 47 | /// 48 | /// match conn.query_drop("LOAD DATA LOCAL INFILE 'file_name' INTO TABLE mysql.tbl") { 49 | /// Ok(_) => (), 50 | /// Err(Error::MySqlError(ref e)) if e.code == 1148 => { 51 | /// // functionality is not supported by the server 52 | /// return Ok(()); 53 | /// } 54 | /// err => { 55 | /// err.unwrap(); 56 | /// } 57 | /// } 58 | /// 59 | /// let mut row_num = 0; 60 | /// let result: Vec = conn.query("SELECT * FROM mysql.tbl").unwrap(); 61 | /// assert_eq!( 62 | /// result, 63 | /// vec!["row1: file name is file_name".to_string(), "row2: foobar".to_string()], 64 | /// ); 65 | /// # }); 66 | /// ``` 67 | #[derive(Clone)] 68 | pub struct LocalInfileHandler(pub(crate) LocalInfileInner); 69 | 70 | impl LocalInfileHandler { 71 | pub fn new(f: F) -> Self 72 | where 73 | F: for<'a> FnMut(&'a [u8], &'a mut LocalInfile<'_>) -> io::Result<()> + Send + 'static, 74 | { 75 | LocalInfileHandler(Arc::new(Mutex::new(f))) 76 | } 77 | } 78 | 79 | impl PartialEq for LocalInfileHandler { 80 | fn eq(&self, other: &LocalInfileHandler) -> bool { 81 | std::ptr::eq(&*self.0, &*other.0) 82 | } 83 | } 84 | 85 | impl Eq for LocalInfileHandler {} 86 | 87 | impl fmt::Debug for LocalInfileHandler { 88 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 89 | write!(f, "LocalInfileHandler(...)") 90 | } 91 | } 92 | 93 | /// Local in-file stream. 94 | /// The callback will be passed a reference to this stream, which it 95 | /// should use to write the contents of the requested file. 96 | /// See [LocalInfileHandler](struct.LocalInfileHandler.html) documentation for example. 97 | #[derive(Debug)] 98 | pub struct LocalInfile<'a> { 99 | buffer: io::Cursor<&'a mut [u8]>, 100 | conn: &'a mut Conn, 101 | } 102 | 103 | impl<'a> LocalInfile<'a> { 104 | pub(crate) const BUFFER_SIZE: usize = 4096; 105 | 106 | pub(crate) fn new(buffer: &'a mut [u8; LocalInfile::BUFFER_SIZE], conn: &'a mut Conn) -> Self { 107 | Self { 108 | buffer: io::Cursor::new(buffer), 109 | conn, 110 | } 111 | } 112 | } 113 | 114 | impl io::Write for LocalInfile<'_> { 115 | fn write(&mut self, buf: &[u8]) -> io::Result { 116 | if self.buffer.position() == Self::BUFFER_SIZE as u64 { 117 | self.flush()?; 118 | } 119 | self.buffer.write(buf) 120 | } 121 | 122 | fn flush(&mut self) -> io::Result<()> { 123 | let n = self.buffer.position() as usize; 124 | if n > 0 { 125 | let mut range = &self.buffer.get_ref()[..n]; 126 | self.conn 127 | .write_packet(&mut range) 128 | .map_err(|e| io::Error::new(io::ErrorKind::Other, Box::new(e)))?; 129 | } 130 | self.buffer.set_position(0); 131 | Ok(()) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/conn/opts/native_tls_opts.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "native-tls")] 2 | 3 | use native_tls::Identity; 4 | 5 | use std::{borrow::Cow, path::Path}; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 8 | pub struct ClientIdentity { 9 | pkcs12_path: Cow<'static, Path>, 10 | password: Option>, 11 | } 12 | 13 | impl ClientIdentity { 14 | /// Creates new identity with the given path to the pkcs12 archive. 15 | pub fn new(pkcs12_path: T) -> Self 16 | where 17 | T: Into>, 18 | { 19 | Self { 20 | pkcs12_path: pkcs12_path.into(), 21 | password: None, 22 | } 23 | } 24 | 25 | /// Sets the archive password. 26 | pub fn with_password(mut self, pass: T) -> Self 27 | where 28 | T: Into>, 29 | { 30 | self.password = Some(pass.into()); 31 | self 32 | } 33 | 34 | /// Returns the pkcs12 archive path. 35 | pub fn pkcs12_path(&self) -> &Path { 36 | self.pkcs12_path.as_ref() 37 | } 38 | 39 | /// Returns the archive password. 40 | pub fn password(&self) -> Option<&str> { 41 | self.password.as_ref().map(AsRef::as_ref) 42 | } 43 | 44 | pub(crate) fn load(&self) -> crate::Result { 45 | let der = std::fs::read(self.pkcs12_path.as_ref())?; 46 | Ok(Identity::from_pkcs12( 47 | &der, 48 | self.password.as_deref().unwrap_or(""), 49 | )?) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/conn/opts/pool_opts.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | macro_rules! const_assert { 10 | ($name:ident, $($xs:expr),+ $(,)*) => { 11 | #[allow(unknown_lints, clippy::eq_op)] 12 | const $name: [(); 0 - !($($xs)&&+) as usize] = []; 13 | }; 14 | } 15 | 16 | /// Connection pool options. 17 | /// 18 | /// ``` 19 | /// # use mysql::{PoolOpts, PoolConstraints}; 20 | /// # use std::time::Duration; 21 | /// let pool_opts = PoolOpts::default() 22 | /// .with_constraints(PoolConstraints::new(15, 30).unwrap()) 23 | /// .with_reset_connection(false); 24 | /// ``` 25 | #[derive(Debug, Clone, Eq, PartialEq, Hash)] 26 | pub struct PoolOpts { 27 | constraints: PoolConstraints, 28 | reset_connection: bool, 29 | check_health: bool, 30 | } 31 | 32 | impl PoolOpts { 33 | /// Calls `Self::default`. 34 | pub fn new() -> Self { 35 | Self::default() 36 | } 37 | 38 | /// Creates the default [`PoolOpts`] with the given constraints. 39 | pub fn with_constraints(mut self, constraints: PoolConstraints) -> Self { 40 | self.constraints = constraints; 41 | self 42 | } 43 | 44 | /// Returns pool constraints. 45 | pub fn constraints(&self) -> PoolConstraints { 46 | self.constraints 47 | } 48 | 49 | /// Sets whether to reset the connection upon returning it to a pool (defaults to `true`). 50 | /// 51 | /// Default behavior increases reliability but comes with cons: 52 | /// 53 | /// * reset procedure removes all prepared statements, i.e. kills prepared statements cache 54 | /// * connection reset is quite fast but requires additional client-server roundtrip 55 | /// (might require re-authentication for older servers) 56 | /// 57 | /// The purpose of the reset procedure is to: 58 | /// 59 | /// * rollback any opened transactions 60 | /// * reset transaction isolation level 61 | /// * reset session variables 62 | /// * delete user variables 63 | /// * remove temporary tables 64 | /// * remove all PREPARE statement (this action kills prepared statements cache) 65 | /// 66 | /// So to increase overall performance you can safely opt-out of the default behavior 67 | /// if you are not willing to change the session state in an unpleasant way. 68 | /// 69 | /// It is also possible to selectively opt-in/out using [`crate::PooledConn::reset_connection`]. 70 | /// 71 | /// # Connection URL 72 | /// 73 | /// You can use `reset_connection` URL parameter to set this value. E.g. 74 | /// 75 | /// ``` 76 | /// # use mysql::*; 77 | /// # use std::time::Duration; 78 | /// # fn main() -> Result<()> { 79 | /// let opts = Opts::from_url("mysql://localhost/db?reset_connection=false")?; 80 | /// assert_eq!(opts.get_pool_opts().reset_connection(), false); 81 | /// # Ok(()) } 82 | /// ``` 83 | pub fn with_reset_connection(mut self, reset_connection: bool) -> Self { 84 | self.reset_connection = reset_connection; 85 | self 86 | } 87 | 88 | /// Returns the `reset_connection` value (see [`PoolOpts::with_reset_connection`]). 89 | pub fn reset_connection(&self) -> bool { 90 | self.reset_connection 91 | } 92 | 93 | /// Sets whether to check connection health upon retrieving it from a pool (defaults to `true`). 94 | /// 95 | /// If `true`, then `Conn::ping` will be invoked on a non-fresh pooled connection. 96 | /// 97 | /// # Connection URL 98 | /// 99 | /// Use `check_health` URL parameter to set this value. E.g. 100 | /// 101 | /// ``` 102 | /// # use mysql::*; 103 | /// # use std::time::Duration; 104 | /// # fn main() -> Result<()> { 105 | /// let opts = Opts::from_url("mysql://localhost/db?check_health=false")?; 106 | /// assert_eq!(opts.get_pool_opts().check_health(), false); 107 | /// # Ok(()) } 108 | /// ``` 109 | pub fn with_check_health(mut self, check_health: bool) -> Self { 110 | self.check_health = check_health; 111 | self 112 | } 113 | 114 | pub fn check_health(&self) -> bool { 115 | self.check_health 116 | } 117 | } 118 | 119 | impl Default for PoolOpts { 120 | fn default() -> Self { 121 | Self { 122 | constraints: PoolConstraints::DEFAULT, 123 | reset_connection: true, 124 | check_health: true, 125 | } 126 | } 127 | } 128 | 129 | /// Connection pool constraints. 130 | /// 131 | /// This type stores `min` and `max` constraints for [`crate::Pool`] and ensures that `min <= max`. 132 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 133 | pub struct PoolConstraints { 134 | min: usize, 135 | max: usize, 136 | } 137 | 138 | const_assert!( 139 | _DEFAULT_POOL_CONSTRAINTS_ARE_CORRECT, 140 | PoolConstraints::DEFAULT.min <= PoolConstraints::DEFAULT.max, 141 | ); 142 | 143 | pub struct Assert; 144 | impl Assert { 145 | pub const LEQ: usize = R - L; 146 | } 147 | 148 | #[allow(path_statements)] 149 | pub const fn gte() { 150 | #[allow(clippy::no_effect)] 151 | Assert::::LEQ; 152 | } 153 | 154 | impl PoolConstraints { 155 | /// Default pool constraints. 156 | pub const DEFAULT: PoolConstraints = PoolConstraints { min: 10, max: 100 }; 157 | 158 | /// Creates new [`PoolConstraints`] if constraints are valid (`min <= max`). 159 | /// 160 | /// # Connection URL 161 | /// 162 | /// You can use `pool_min` and `pool_max` URL parameters to define pool constraints. 163 | /// 164 | /// ``` 165 | /// # use mysql::*; 166 | /// # fn main() -> Result<()> { 167 | /// let opts = Opts::from_url("mysql://localhost/db?pool_min=0&pool_max=151")?; 168 | /// assert_eq!(opts.get_pool_opts().constraints(), PoolConstraints::new(0, 151).unwrap()); 169 | /// # Ok(()) } 170 | /// ``` 171 | pub fn new(min: usize, max: usize) -> Option { 172 | if min <= max { 173 | Some(PoolConstraints { min, max }) 174 | } else { 175 | None 176 | } 177 | } 178 | 179 | pub const fn new_const() -> PoolConstraints { 180 | gte::(); 181 | PoolConstraints { min: MIN, max: MAX } 182 | } 183 | 184 | /// Lower bound of this pool constraints. 185 | pub const fn min(&self) -> usize { 186 | self.min 187 | } 188 | 189 | /// Upper bound of this pool constraints. 190 | pub const fn max(&self) -> usize { 191 | self.max 192 | } 193 | } 194 | 195 | impl Default for PoolConstraints { 196 | fn default() -> Self { 197 | PoolConstraints::DEFAULT 198 | } 199 | } 200 | 201 | impl From for (usize, usize) { 202 | /// Transforms constraints to a pair of `(min, max)`. 203 | fn from(PoolConstraints { min, max }: PoolConstraints) -> Self { 204 | (min, max) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/conn/opts/rustls_opts.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "rustls")] 2 | 3 | use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs1KeyDer}; 4 | use rustls_pemfile::{certs, ec_private_keys, pkcs8_private_keys, rsa_private_keys}; 5 | 6 | use std::{borrow::Cow, path::Path}; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 9 | pub struct ClientIdentity { 10 | cert_chain_path: Cow<'static, Path>, 11 | priv_key_path: Cow<'static, Path>, 12 | } 13 | 14 | impl ClientIdentity { 15 | /// Creates new identity. 16 | /// 17 | /// `cert_chain_path` - path to a certificate chain (in PEM or DER) 18 | /// `priv_key_path` - path to a private key (in DER or PEM) (it'll take the first one) 19 | pub fn new(cert_chain_path: T, priv_key_path: U) -> Self 20 | where 21 | T: Into>, 22 | U: Into>, 23 | { 24 | Self { 25 | cert_chain_path: cert_chain_path.into(), 26 | priv_key_path: priv_key_path.into(), 27 | } 28 | } 29 | 30 | /// Sets the certificate chain path (in DER or PEM). 31 | pub fn with_cert_chain_path(mut self, cert_chain_path: T) -> Self 32 | where 33 | T: Into>, 34 | { 35 | self.cert_chain_path = cert_chain_path.into(); 36 | self 37 | } 38 | 39 | /// Sets the private key path (in DER or PEM) (it'll take the first one). 40 | pub fn with_priv_key_path(mut self, priv_key_path: T) -> Self 41 | where 42 | T: Into>, 43 | { 44 | self.priv_key_path = priv_key_path.into(); 45 | self 46 | } 47 | 48 | /// Returns the certificate chain path. 49 | pub fn cert_chain_path(&self) -> &Path { 50 | self.cert_chain_path.as_ref() 51 | } 52 | 53 | /// Returns the private key path. 54 | pub fn priv_key_path(&self) -> &Path { 55 | self.priv_key_path.as_ref() 56 | } 57 | 58 | pub(crate) fn load( 59 | &self, 60 | ) -> crate::Result<(Vec>, PrivateKeyDer<'static>)> { 61 | let cert_data = std::fs::read(self.cert_chain_path.as_ref())?; 62 | let key_data = std::fs::read(self.priv_key_path.as_ref())?; 63 | 64 | let mut cert_chain = Vec::new(); 65 | for cert in certs(&mut &*cert_data) { 66 | cert_chain.push(cert?.to_owned()); 67 | } 68 | if cert_chain.is_empty() && !cert_data.is_empty() { 69 | cert_chain.push(CertificateDer::from(cert_data)); 70 | } 71 | 72 | let mut priv_key = None; 73 | 74 | for key in rsa_private_keys(&mut &*key_data).take(1) { 75 | priv_key = Some(PrivateKeyDer::Pkcs1(key?.clone_key())); 76 | } 77 | 78 | if priv_key.is_none() { 79 | for key in pkcs8_private_keys(&mut &*key_data).take(1) { 80 | priv_key = Some(PrivateKeyDer::Pkcs8(key?.clone_key())) 81 | } 82 | } 83 | 84 | if priv_key.is_none() { 85 | for key in ec_private_keys(&mut &*key_data).take(1) { 86 | priv_key = Some(PrivateKeyDer::Sec1(key?.clone_key())) 87 | } 88 | } 89 | 90 | if let Some(priv_key) = priv_key { 91 | return Ok((cert_chain, priv_key)); 92 | } 93 | 94 | match PrivateKeyDer::try_from(key_data.as_slice()) { 95 | Ok(key) => Ok((cert_chain, key.clone_key())), 96 | Err(_) => Ok(( 97 | cert_chain, 98 | PrivateKeyDer::Pkcs1(PrivatePkcs1KeyDer::from(key_data)), 99 | )), 100 | } 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use std::path::Path; 107 | 108 | use rustls::pki_types::PrivateKeyDer; 109 | 110 | use crate::ClientIdentity; 111 | 112 | #[test] 113 | fn load_pkcs1() { 114 | let (_certs, key_pem) = ClientIdentity::new( 115 | Path::new("tests/client.crt"), 116 | Path::new("tests/client-key.pem"), 117 | ) 118 | .load() 119 | .unwrap(); 120 | assert!(matches!(key_pem, PrivateKeyDer::Pkcs1(_))); 121 | 122 | let (_certs, key_der) = ClientIdentity::new( 123 | Path::new("tests/client.crt"), 124 | Path::new("tests/client-key.pem"), 125 | ) 126 | .load() 127 | .unwrap(); 128 | assert!(matches!(key_der, PrivateKeyDer::Pkcs1(_))); 129 | 130 | assert_eq!(key_der, key_pem); 131 | } 132 | 133 | #[test] 134 | fn load_pkcs8() { 135 | let (_certs, key_der) = ClientIdentity::new( 136 | Path::new("tests/client.crt"), 137 | Path::new("tests/client-key.pkcs8.der"), 138 | ) 139 | .load() 140 | .unwrap(); 141 | assert!(matches!(key_der, PrivateKeyDer::Pkcs8(_))); 142 | 143 | let (_certs, key_pem) = ClientIdentity::new( 144 | Path::new("tests/client.crt"), 145 | Path::new("tests/client-key.pkcs8.pem"), 146 | ) 147 | .load() 148 | .unwrap(); 149 | assert!(matches!(key_pem, PrivateKeyDer::Pkcs8(_))); 150 | 151 | assert_eq!(key_der, key_pem); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/conn/pool/inner.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | sync::{ 4 | atomic::{AtomicUsize, Ordering}, 5 | Condvar, Mutex, 6 | }, 7 | }; 8 | 9 | use crate::{Conn, Opts, PoolOpts}; 10 | 11 | #[derive(Debug)] 12 | pub struct Protected { 13 | opts: Opts, 14 | connections: VecDeque, 15 | } 16 | 17 | impl Protected { 18 | fn new(opts: Opts) -> crate::Result { 19 | let constraints = opts.get_pool_opts().constraints(); 20 | 21 | let mut this = Protected { 22 | connections: VecDeque::with_capacity(constraints.max()), 23 | opts, 24 | }; 25 | 26 | for _ in 0..constraints.min() { 27 | this.new_conn()?; 28 | } 29 | 30 | Ok(this) 31 | } 32 | 33 | pub fn new_conn(&mut self) -> crate::Result<()> { 34 | match Conn::new(self.opts.clone()) { 35 | Ok(conn) => { 36 | self.connections.push_back(conn); 37 | Ok(()) 38 | } 39 | Err(err) => Err(err), 40 | } 41 | } 42 | 43 | pub fn take_by_query(&mut self, query: &[u8]) -> Option { 44 | match self 45 | .connections 46 | .iter() 47 | .position(|conn| conn.has_stmt(query)) 48 | { 49 | Some(position) => self.connections.swap_remove_back(position), 50 | None => None, 51 | } 52 | } 53 | 54 | pub fn pop_front(&mut self) -> Option { 55 | self.connections.pop_front() 56 | } 57 | 58 | pub fn push_back(&mut self, conn: Conn) { 59 | self.connections.push_back(conn) 60 | } 61 | } 62 | 63 | pub struct Inner { 64 | protected: (Mutex, Condvar), 65 | pool_opts: PoolOpts, 66 | count: AtomicUsize, 67 | } 68 | 69 | impl Inner { 70 | pub fn increase(&self) { 71 | let prev = self.count.fetch_add(1, Ordering::Relaxed); 72 | debug_assert!(prev < self.max_constraint()); 73 | } 74 | 75 | pub fn decrease(&self) { 76 | let prev = self.count.fetch_sub(1, Ordering::Relaxed); 77 | debug_assert!(prev > 0); 78 | } 79 | 80 | pub fn count(&self) -> usize { 81 | let value = self.count.load(Ordering::Relaxed); 82 | debug_assert!(value <= self.max_constraint()); 83 | value 84 | } 85 | 86 | pub fn is_full(&self) -> bool { 87 | self.count() == self.max_constraint() 88 | } 89 | 90 | pub fn opts(&self) -> &PoolOpts { 91 | &self.pool_opts 92 | } 93 | 94 | pub fn max_constraint(&self) -> usize { 95 | self.pool_opts.constraints().max() 96 | } 97 | 98 | pub fn protected(&self) -> &(Mutex, Condvar) { 99 | &self.protected 100 | } 101 | 102 | pub fn new(opts: Opts) -> crate::Result { 103 | Ok(Self { 104 | count: AtomicUsize::new(opts.get_pool_opts().constraints().min()), 105 | pool_opts: opts.get_pool_opts().clone(), 106 | protected: (Mutex::new(Protected::new(opts)?), Condvar::new()), 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/conn/pool/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use std::{ 10 | fmt, 11 | ops::Deref, 12 | sync::Arc, 13 | time::{Duration, Instant}, 14 | }; 15 | 16 | use crate::{ 17 | conn::query_result::{Binary, Text}, 18 | prelude::*, 19 | ChangeUserOpts, Conn, DriverError, LocalInfileHandler, Opts, Params, QueryResult, Result, 20 | Statement, Transaction, TxOpts, 21 | }; 22 | 23 | mod inner; 24 | 25 | /// Thread-safe cloneable smart pointer to a connection pool. 26 | /// 27 | /// However you can prepare statements directly on `Pool` without 28 | /// invoking [`Pool::get_conn`](struct.Pool.html#method.get_conn). 29 | /// 30 | /// `Pool` will hold at least `min` connections and will create as many as `max` 31 | /// connections with possible overhead of one connection per alive thread. 32 | /// 33 | /// Example of multithreaded `Pool` usage: 34 | /// 35 | /// ```rust 36 | /// # mysql::doctest_wrapper!(__result, { 37 | /// # use mysql::*; 38 | /// # use mysql::prelude::*; 39 | /// # let mut conn = Conn::new(get_opts())?; 40 | /// # let pool_opts = PoolOpts::new().with_constraints(PoolConstraints::new_const::<5, 10>()); 41 | /// # let opts = get_opts().pool_opts(pool_opts); 42 | /// let pool = Pool::new(opts).unwrap(); 43 | /// let mut threads = Vec::new(); 44 | /// 45 | /// for _ in 0..1000 { 46 | /// let pool = pool.clone(); 47 | /// threads.push(std::thread::spawn(move || { 48 | /// let mut conn = pool.get_conn().unwrap(); 49 | /// let result: u8 = conn.query_first("SELECT 1").unwrap().unwrap(); 50 | /// assert_eq!(result, 1_u8); 51 | /// })); 52 | /// } 53 | /// 54 | /// for t in threads.into_iter() { 55 | /// assert!(t.join().is_ok()); 56 | /// } 57 | /// # }); 58 | /// ``` 59 | /// 60 | /// For more info on how to work with mysql connection please look at 61 | /// [`PooledConn`](struct.PooledConn.html) documentation. 62 | #[derive(Clone)] 63 | pub struct Pool { 64 | inner: Arc, 65 | } 66 | 67 | impl Pool { 68 | /// Will return connection taken from a pool. 69 | /// 70 | /// Will wait til timeout if `timeout_ms` is `Some(_)` 71 | fn _get_conn>( 72 | &self, 73 | stmt: Option, 74 | timeout: Option, 75 | mut call_ping: bool, 76 | ) -> Result { 77 | let times = timeout.map(|timeout| (Instant::now(), timeout)); 78 | 79 | let (protected, condvar) = self.inner.protected(); 80 | 81 | let conn = if !self.inner.opts().reset_connection() { 82 | // stmt cache considered enabled if reset_connection is false 83 | if let Some(ref query) = stmt { 84 | protected.lock()?.take_by_query(query.as_ref()) 85 | } else { 86 | None 87 | } 88 | } else { 89 | None 90 | }; 91 | 92 | let mut conn = if let Some(conn) = conn { 93 | conn 94 | } else { 95 | let mut protected = protected.lock()?; 96 | loop { 97 | if let Some(conn) = protected.pop_front() { 98 | drop(protected); 99 | break conn; 100 | } else if self.inner.is_full() { 101 | protected = if let Some((start, timeout)) = times { 102 | if start.elapsed() > timeout { 103 | return Err(DriverError::Timeout.into()); 104 | } 105 | condvar.wait_timeout(protected, timeout)?.0 106 | } else { 107 | condvar.wait(protected)? 108 | } 109 | } else { 110 | protected.new_conn()?; 111 | self.inner.increase(); 112 | // we do not have to call ping for a fresh connection 113 | call_ping = false; 114 | } 115 | } 116 | }; 117 | 118 | if call_ping && self.inner.opts().check_health() && conn.ping().is_err() { 119 | // existing connection seem to be dead, retrying.. 120 | self.inner.decrease(); 121 | return self._get_conn(stmt, timeout, call_ping); 122 | } 123 | 124 | Ok(PooledConn { 125 | pool: self.clone(), 126 | conn: Some(conn), 127 | }) 128 | } 129 | 130 | /// Creates new pool with the given options (see [`Opts`]). 131 | pub fn new(opts: T) -> Result 132 | where 133 | Opts: TryFrom, 134 | crate::Error: From, 135 | { 136 | Ok(Pool { 137 | inner: Arc::new(inner::Inner::new(Opts::try_from(opts)?)?), 138 | }) 139 | } 140 | 141 | /// Gives you a [`PooledConn`](struct.PooledConn.html). 142 | pub fn get_conn(&self) -> Result { 143 | self._get_conn(None::, None, true) 144 | } 145 | 146 | /// Will try to get connection for the duration of `timeout`. 147 | /// 148 | /// # Failure 149 | /// This function will return `Error::DriverError(DriverError::Timeout)` if timeout was 150 | /// reached while waiting for new connection to become available. 151 | pub fn try_get_conn(&self, timeout: Duration) -> Result { 152 | self._get_conn(None::, Some(timeout), true) 153 | } 154 | 155 | /// Shortcut for `pool.get_conn()?.start_transaction(..)`. 156 | pub fn start_transaction(&self, tx_opts: TxOpts) -> Result> { 157 | let conn = self._get_conn(None::, None, false)?; 158 | let result = conn.pooled_start_transaction(tx_opts); 159 | match result { 160 | Ok(trans) => Ok(trans), 161 | Err(ref e) if e.is_connectivity_error() => { 162 | let conn = self._get_conn(None::, None, true)?; 163 | conn.pooled_start_transaction(tx_opts) 164 | } 165 | Err(e) => Err(e), 166 | } 167 | } 168 | } 169 | 170 | impl fmt::Debug for Pool { 171 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 172 | write!( 173 | f, 174 | "Pool {{ constraints: {:?}, count: {} }}", 175 | self.inner.opts().constraints(), 176 | self.inner.count(), 177 | ) 178 | } 179 | } 180 | 181 | /// Pooled mysql connection. 182 | /// 183 | /// You should prefer using `prep` along `exec` instead of `query` from the Queryable trait where 184 | /// possible, except cases when statement has no params and when it has no return values or return 185 | /// values which evaluates to `Value::Bytes`. 186 | /// 187 | /// `query` is a part of mysql text protocol, so under the hood you will always receive 188 | /// `Value::Bytes` as a result and `from_value` will need to parse it if you want, for example, `i64` 189 | /// 190 | /// ```rust 191 | /// # mysql::doctest_wrapper!(__result, { 192 | /// # use mysql::*; 193 | /// # use mysql::prelude::*; 194 | /// # let mut conn = Conn::new(get_opts())?; 195 | /// let pool = Pool::new(get_opts()).unwrap(); 196 | /// let mut conn = pool.get_conn().unwrap(); 197 | /// 198 | /// conn.query_first("SELECT 42").map(|result: Option| { 199 | /// let result = result.unwrap(); 200 | /// assert_eq!(result, Value::Bytes(b"42".to_vec())); 201 | /// assert_eq!(from_value::(result), 42i64); 202 | /// }).unwrap(); 203 | /// conn.exec_iter("SELECT 42", ()).map(|mut result| { 204 | /// let cell = result.next().unwrap().unwrap().take(0).unwrap(); 205 | /// assert_eq!(cell, Value::Int(42i64)); 206 | /// assert_eq!(from_value::(cell), 42i64); 207 | /// }).unwrap(); 208 | /// # }); 209 | /// ``` 210 | /// 211 | /// For more info on how to work with query results please look at 212 | /// [`QueryResult`](../struct.QueryResult.html) documentation. 213 | #[derive(Debug)] 214 | pub struct PooledConn { 215 | pool: Pool, 216 | conn: Option, 217 | } 218 | 219 | impl Deref for PooledConn { 220 | type Target = Conn; 221 | 222 | fn deref(&self) -> &Self::Target { 223 | self.conn.as_ref().expect("deref after drop") 224 | } 225 | } 226 | 227 | impl Drop for PooledConn { 228 | fn drop(&mut self) { 229 | if let Some(mut conn) = self.conn.take() { 230 | match conn.cleanup_for_pool() { 231 | Ok(_) => { 232 | let (protected, condvar) = self.pool.inner.protected(); 233 | match protected.lock() { 234 | Ok(mut protected) => { 235 | protected.push_back(conn); 236 | drop(protected); 237 | condvar.notify_one(); 238 | } 239 | Err(_) => { 240 | // everything is broken 241 | self.pool.inner.decrease(); 242 | } 243 | } 244 | } 245 | Err(_) => { 246 | // the connection is broken 247 | self.pool.inner.decrease(); 248 | } 249 | } 250 | } 251 | } 252 | } 253 | 254 | impl PooledConn { 255 | /// Redirects to 256 | /// [`Conn#start_transaction`](struct.Conn.html#method.start_transaction) 257 | pub fn start_transaction(&mut self, tx_opts: TxOpts) -> Result { 258 | self.conn.as_mut().unwrap().start_transaction(tx_opts) 259 | } 260 | 261 | /// Turns this connection into a binlog stream (see [`Conn::get_binlog_stream`]). 262 | #[cfg(feature = "binlog")] 263 | #[cfg_attr(docsrs, doc(cfg(feature = "binlog")))] 264 | pub fn get_binlog_stream( 265 | mut self, 266 | request: crate::BinlogRequest<'_>, 267 | ) -> Result { 268 | self.conn.take().unwrap().get_binlog_stream(request) 269 | } 270 | 271 | /// Unwraps wrapped [`Conn`](struct.Conn.html). 272 | pub fn unwrap(mut self) -> Conn { 273 | self.conn.take().unwrap() 274 | } 275 | 276 | fn pooled_start_transaction(mut self, tx_opts: TxOpts) -> Result> { 277 | self.as_mut()._start_transaction(tx_opts)?; 278 | Ok(Transaction::new(self.into())) 279 | } 280 | 281 | /// A way to override default local infile handler for this pooled connection. Destructor will 282 | /// restore original handler before returning connection to a pool. 283 | /// See [`Conn::set_local_infile_handler`](struct.Conn.html#method.set_local_infile_handler). 284 | pub fn set_local_infile_handler(&mut self, handler: Option) { 285 | self.conn 286 | .as_mut() 287 | .unwrap() 288 | .set_local_infile_handler(handler); 289 | } 290 | 291 | /// Invokes `COM_CHANGE_USER` (see [`Conn::change_user`] docs). 292 | pub fn change_user(&mut self) -> Result<()> { 293 | self.conn 294 | .as_mut() 295 | .unwrap() 296 | .change_user(ChangeUserOpts::default()) 297 | } 298 | 299 | /// Turns on/off automatic connection reset upon return to a pool (see [`Opts::get_pool_opts`]). 300 | /// 301 | /// Initial value is taken from [`crate::PoolOpts::reset_connection`]. 302 | pub fn reset_connection(&mut self, reset_connection: bool) { 303 | if let Some(conn) = self.conn.as_mut() { 304 | conn.0.reset_upon_return = reset_connection; 305 | } 306 | } 307 | } 308 | 309 | impl AsRef for PooledConn { 310 | fn as_ref(&self) -> &Conn { 311 | self.conn.as_ref().unwrap() 312 | } 313 | } 314 | 315 | impl AsMut for PooledConn { 316 | fn as_mut(&mut self) -> &mut Conn { 317 | self.conn.as_mut().unwrap() 318 | } 319 | } 320 | 321 | impl Queryable for PooledConn { 322 | fn query_iter>(&mut self, query: T) -> Result> { 323 | self.conn.as_mut().unwrap().query_iter(query) 324 | } 325 | 326 | fn prep>(&mut self, query: T) -> Result { 327 | self.conn.as_mut().unwrap().prep(query) 328 | } 329 | 330 | fn close(&mut self, stmt: Statement) -> Result<()> { 331 | self.conn.as_mut().unwrap().close(stmt) 332 | } 333 | 334 | fn exec_iter(&mut self, stmt: S, params: P) -> Result> 335 | where 336 | S: AsStatement, 337 | P: Into, 338 | { 339 | self.conn.as_mut().unwrap().exec_iter(stmt, params) 340 | } 341 | } 342 | 343 | #[cfg(test)] 344 | #[allow(non_snake_case)] 345 | mod test { 346 | mod pool { 347 | use std::{thread, time::Duration}; 348 | 349 | use crate::{ 350 | from_value, prelude::*, test_misc::get_opts, DriverError, Error, OptsBuilder, Pool, 351 | PoolConstraints, PoolOpts, TxOpts, Value, 352 | }; 353 | 354 | #[test] 355 | fn multiple_pools_should_work() { 356 | let pool = Pool::new(get_opts()).unwrap(); 357 | pool.get_conn() 358 | .unwrap() 359 | .exec_drop("DROP DATABASE IF EXISTS A", ()) 360 | .unwrap(); 361 | pool.get_conn() 362 | .unwrap() 363 | .exec_drop("CREATE DATABASE A", ()) 364 | .unwrap(); 365 | pool.get_conn() 366 | .unwrap() 367 | .exec_drop("DROP TABLE IF EXISTS A.a", ()) 368 | .unwrap(); 369 | pool.get_conn() 370 | .unwrap() 371 | .exec_drop("CREATE TABLE IF NOT EXISTS A.a (id INT)", ()) 372 | .unwrap(); 373 | pool.get_conn() 374 | .unwrap() 375 | .exec_drop("INSERT INTO A.a VALUES (1)", ()) 376 | .unwrap(); 377 | let opts = OptsBuilder::from_opts(get_opts()).db_name(Some("A")); 378 | let pool2 = Pool::new(opts).unwrap(); 379 | let count: u8 = pool2 380 | .get_conn() 381 | .unwrap() 382 | .exec_first("SELECT COUNT(*) FROM a", ()) 383 | .unwrap() 384 | .unwrap(); 385 | assert_eq!(1, count); 386 | pool.get_conn() 387 | .unwrap() 388 | .exec_drop("DROP DATABASE A", ()) 389 | .unwrap(); 390 | } 391 | 392 | struct A { 393 | pool: Pool, 394 | x: u32, 395 | } 396 | 397 | impl A { 398 | fn add(&mut self) { 399 | self.x += 1; 400 | } 401 | } 402 | 403 | #[test] 404 | fn should_fix_connectivity_errors_on_prepare() { 405 | let pool = Pool::new(get_opts().pool_opts( 406 | PoolOpts::default().with_constraints(PoolConstraints::new_const::<2, 2>()), 407 | )) 408 | .unwrap(); 409 | let mut conn = pool.get_conn().unwrap(); 410 | 411 | let id: u32 = pool 412 | .get_conn() 413 | .unwrap() 414 | .exec_first("SELECT CONNECTION_ID();", ()) 415 | .unwrap() 416 | .unwrap(); 417 | 418 | conn.query_drop(&*format!("KILL {}", id)).unwrap(); 419 | thread::sleep(Duration::from_millis(250)); 420 | pool.get_conn() 421 | .unwrap() 422 | .prep("SHOW FULL PROCESSLIST") 423 | .unwrap(); 424 | } 425 | 426 | #[test] 427 | fn should_fix_connectivity_errors_on_prep_exec() { 428 | let pool = Pool::new(get_opts().pool_opts( 429 | PoolOpts::default().with_constraints(PoolConstraints::new_const::<2, 2>()), 430 | )) 431 | .unwrap(); 432 | let mut conn = pool.get_conn().unwrap(); 433 | 434 | let id: u32 = pool 435 | .get_conn() 436 | .unwrap() 437 | .exec_first("SELECT CONNECTION_ID();", ()) 438 | .unwrap() 439 | .unwrap(); 440 | 441 | conn.query_drop(&*format!("KILL {}", id)).unwrap(); 442 | thread::sleep(Duration::from_millis(250)); 443 | pool.get_conn() 444 | .unwrap() 445 | .exec_drop("SHOW FULL PROCESSLIST", ()) 446 | .unwrap(); 447 | } 448 | #[test] 449 | fn should_fix_connectivity_errors_on_start_transaction() { 450 | let pool = Pool::new(get_opts().pool_opts( 451 | PoolOpts::default().with_constraints(PoolConstraints::new_const::<2, 2>()), 452 | )) 453 | .unwrap(); 454 | let mut conn = pool.get_conn().unwrap(); 455 | 456 | let id: u32 = pool 457 | .get_conn() 458 | .unwrap() 459 | .exec_first("SELECT CONNECTION_ID();", ()) 460 | .unwrap() 461 | .unwrap(); 462 | 463 | conn.query_drop(&*format!("KILL {}", id)).unwrap(); 464 | thread::sleep(Duration::from_millis(250)); 465 | pool.start_transaction(TxOpts::default()).unwrap(); 466 | } 467 | #[test] 468 | fn should_execute_queries_on_PooledConn() { 469 | let pool = Pool::new(get_opts()).unwrap(); 470 | let mut threads = Vec::new(); 471 | for _ in 0usize..10 { 472 | let pool = pool.clone(); 473 | threads.push(thread::spawn(move || { 474 | let conn = pool.get_conn(); 475 | assert!(conn.is_ok()); 476 | let mut conn = conn.unwrap(); 477 | conn.query_drop("SELECT 1").unwrap(); 478 | })); 479 | } 480 | for t in threads.into_iter() { 481 | assert!(t.join().is_ok()); 482 | } 483 | } 484 | #[test] 485 | fn should_timeout_if_no_connections_available() { 486 | let pool = Pool::new(get_opts().pool_opts( 487 | PoolOpts::default().with_constraints(PoolConstraints::new_const::<0, 1>()), 488 | )) 489 | .unwrap(); 490 | let conn1 = pool.try_get_conn(Duration::from_millis(357)).unwrap(); 491 | let conn2 = pool.try_get_conn(Duration::from_millis(357)); 492 | assert!(conn2.is_err()); 493 | match conn2 { 494 | Err(Error::DriverError(DriverError::Timeout)) => (), 495 | _ => panic!("Timeout error expected"), 496 | } 497 | drop(conn1); 498 | assert!(pool.try_get_conn(Duration::from_millis(357)).is_ok()); 499 | } 500 | 501 | #[test] 502 | fn should_execute_statements_on_PooledConn() { 503 | let pool = Pool::new(get_opts()).unwrap(); 504 | let mut threads = Vec::new(); 505 | for _ in 0usize..10 { 506 | let pool = pool.clone(); 507 | threads.push(thread::spawn(move || { 508 | let mut conn = pool.get_conn().unwrap(); 509 | let stmt = conn.prep("SELECT 1").unwrap(); 510 | conn.exec_drop(&stmt, ()).unwrap(); 511 | })); 512 | } 513 | for t in threads.into_iter() { 514 | assert!(t.join().is_ok()); 515 | } 516 | 517 | let pool = Pool::new(get_opts()).unwrap(); 518 | let mut threads = Vec::new(); 519 | for _ in 0usize..10 { 520 | let pool = pool.clone(); 521 | threads.push(thread::spawn(move || { 522 | let mut conn = pool.get_conn().unwrap(); 523 | conn.exec_drop("SELECT ?", (1,)).unwrap(); 524 | })); 525 | } 526 | for t in threads.into_iter() { 527 | assert!(t.join().is_ok()); 528 | } 529 | } 530 | 531 | #[test] 532 | #[allow(unused_variables)] 533 | fn should_start_transaction_on_Pool() { 534 | let pool = Pool::new( 535 | get_opts().pool_opts( 536 | PoolOpts::default() 537 | .with_constraints(PoolConstraints::new_const::<1, 10>()) 538 | .with_reset_connection(false), 539 | ), 540 | ) 541 | .unwrap(); 542 | pool.get_conn() 543 | .unwrap() 544 | .query_drop("CREATE TEMPORARY TABLE mysql.tbl(a INT)") 545 | .unwrap(); 546 | pool.start_transaction(TxOpts::default()) 547 | .and_then(|mut t| { 548 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(1)").unwrap(); 549 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(2)").unwrap(); 550 | t.commit() 551 | }) 552 | .unwrap(); 553 | assert_eq!( 554 | pool.get_conn() 555 | .unwrap() 556 | .query_first::("SELECT COUNT(a) FROM mysql.tbl") 557 | .unwrap() 558 | .unwrap(), 559 | 2_u8 560 | ); 561 | pool.start_transaction(TxOpts::default()) 562 | .and_then(|mut t| { 563 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(1)").unwrap(); 564 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(2)").unwrap(); 565 | t.rollback() 566 | }) 567 | .unwrap(); 568 | assert_eq!( 569 | pool.get_conn() 570 | .unwrap() 571 | .query_first::("SELECT COUNT(a) FROM mysql.tbl") 572 | .unwrap() 573 | .unwrap(), 574 | 2_u8 575 | ); 576 | pool.start_transaction(TxOpts::default()) 577 | .map(|mut t| { 578 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(1)").unwrap(); 579 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(2)").unwrap(); 580 | }) 581 | .unwrap(); 582 | assert_eq!( 583 | pool.get_conn() 584 | .unwrap() 585 | .query_first::("SELECT COUNT(a) FROM mysql.tbl") 586 | .unwrap() 587 | .unwrap(), 588 | 2_u8 589 | ); 590 | let mut a = A { pool, x: 0 }; 591 | let transaction = a.pool.start_transaction(TxOpts::default()).unwrap(); 592 | a.add(); 593 | } 594 | 595 | #[test] 596 | fn should_reuse_connections() -> crate::Result<()> { 597 | let pool = Pool::new(get_opts().pool_opts( 598 | PoolOpts::default().with_constraints(PoolConstraints::new_const::<1, 1>()), 599 | ))?; 600 | let mut conn = pool.get_conn()?; 601 | 602 | let server_version = conn.server_version(); 603 | let connection_id = conn.connection_id(); 604 | 605 | for _ in 0..16 { 606 | drop(conn); 607 | conn = pool.get_conn()?; 608 | println!("CONN connection_id={}", conn.connection_id()); 609 | assert!(conn.connection_id() == connection_id || server_version < (5, 7, 2)); 610 | } 611 | 612 | Ok(()) 613 | } 614 | 615 | #[test] 616 | fn should_start_transaction_on_PooledConn() { 617 | let pool = Pool::new(get_opts()).unwrap(); 618 | let mut conn = pool.get_conn().unwrap(); 619 | conn.query_drop("CREATE TEMPORARY TABLE mysql.tbl(a INT)") 620 | .unwrap(); 621 | conn.start_transaction(TxOpts::default()) 622 | .and_then(|mut t| { 623 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(1)").unwrap(); 624 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(2)").unwrap(); 625 | t.commit() 626 | }) 627 | .unwrap(); 628 | for x in conn.query_iter("SELECT COUNT(a) FROM mysql.tbl").unwrap() { 629 | let mut x = x.unwrap(); 630 | assert_eq!(from_value::(x.take(0).unwrap()), 2u8); 631 | } 632 | conn.start_transaction(TxOpts::default()) 633 | .and_then(|mut t| { 634 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(1)").unwrap(); 635 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(2)").unwrap(); 636 | t.rollback() 637 | }) 638 | .unwrap(); 639 | for x in conn.query_iter("SELECT COUNT(a) FROM mysql.tbl").unwrap() { 640 | let mut x = x.unwrap(); 641 | assert_eq!(from_value::(x.take(0).unwrap()), 2u8); 642 | } 643 | conn.start_transaction(TxOpts::default()) 644 | .map(|mut t| { 645 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(1)").unwrap(); 646 | t.query_drop("INSERT INTO mysql.tbl(a) VALUES(2)").unwrap(); 647 | }) 648 | .unwrap(); 649 | for x in conn.query_iter("SELECT COUNT(a) FROM mysql.tbl").unwrap() { 650 | let mut x = x.unwrap(); 651 | assert_eq!(from_value::(x.take(0).unwrap()), 2u8); 652 | } 653 | } 654 | 655 | #[test] 656 | fn should_opt_out_of_connection_reset() { 657 | let pool_opts = PoolOpts::new().with_constraints(PoolConstraints::new_const::<1, 1>()); 658 | let opts = get_opts().pool_opts(pool_opts.clone()); 659 | 660 | let pool = Pool::new(opts.clone()).unwrap(); 661 | 662 | let mut conn = pool.get_conn().unwrap(); 663 | assert_eq!( 664 | conn.query_first::("SELECT @foo").unwrap(), 665 | Some(Value::NULL) 666 | ); 667 | conn.query_drop("SET @foo = 'foo'").unwrap(); 668 | assert_eq!( 669 | conn.query_first::("SELECT @foo") 670 | .unwrap() 671 | .unwrap(), 672 | "foo", 673 | ); 674 | drop(conn); 675 | 676 | conn = pool.get_conn().unwrap(); 677 | assert_eq!( 678 | conn.query_first::("SELECT @foo").unwrap(), 679 | Some(Value::NULL) 680 | ); 681 | conn.query_drop("SET @foo = 'foo'").unwrap(); 682 | conn.reset_connection(false); 683 | drop(conn); 684 | 685 | conn = pool.get_conn().unwrap(); 686 | assert_eq!( 687 | conn.query_first::("SELECT @foo") 688 | .unwrap() 689 | .unwrap(), 690 | "foo", 691 | ); 692 | drop(conn); 693 | 694 | let pool = Pool::new(opts.pool_opts(pool_opts.with_reset_connection(false))).unwrap(); 695 | conn = pool.get_conn().unwrap(); 696 | conn.query_drop("SET @foo = 'foo'").unwrap(); 697 | drop(conn); 698 | conn = pool.get_conn().unwrap(); 699 | assert_eq!( 700 | conn.query_first::("SELECT @foo") 701 | .unwrap() 702 | .unwrap(), 703 | "foo", 704 | ); 705 | drop(conn); 706 | } 707 | 708 | #[cfg(feature = "nightly")] 709 | mod bench { 710 | use test; 711 | 712 | use std::thread; 713 | 714 | use crate::{prelude::*, test_misc::get_opts, Pool}; 715 | 716 | #[bench] 717 | fn many_prepexecs(bencher: &mut test::Bencher) { 718 | let pool = Pool::new(get_opts()).unwrap(); 719 | bencher.iter(|| { 720 | "SELECT 1".with(()).run(&pool).unwrap(); 721 | }); 722 | } 723 | 724 | #[bench] 725 | fn many_prepares_threaded(bencher: &mut test::Bencher) { 726 | let pool = Pool::new(get_opts()).unwrap(); 727 | bencher.iter(|| { 728 | let mut threads = Vec::new(); 729 | for _ in 0..4 { 730 | let pool = pool.clone(); 731 | threads.push(thread::spawn(move || { 732 | for _ in 0..250 { 733 | test::black_box( 734 | "SELECT 1, 'hello world', 123.321, ?, ?, ?" 735 | .with(("hello", "world", 65536)) 736 | .run(&pool) 737 | .unwrap(), 738 | ); 739 | } 740 | })); 741 | } 742 | for t in threads { 743 | t.join().unwrap(); 744 | } 745 | }); 746 | } 747 | 748 | #[bench] 749 | fn many_prepares_threaded_no_cache(bencher: &mut test::Bencher) { 750 | let mut pool = Pool::new(get_opts()).unwrap(); 751 | pool.use_cache(false); 752 | bencher.iter(|| { 753 | let mut threads = Vec::new(); 754 | for _ in 0..4 { 755 | let pool = pool.clone(); 756 | threads.push(thread::spawn(move || { 757 | for _ in 0..250 { 758 | test::black_box( 759 | "SELECT 1, 'hello world', 123.321, ?, ?, ?" 760 | .with(("hello", "world", 65536)) 761 | .run(&pool) 762 | .unwrap(), 763 | ); 764 | } 765 | })); 766 | } 767 | for t in threads { 768 | t.join().unwrap(); 769 | } 770 | }); 771 | } 772 | } 773 | } 774 | } 775 | -------------------------------------------------------------------------------- /src/conn/query.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use mysql_common::row::convert::FromRowError; 10 | 11 | use std::{convert::TryInto, result::Result as StdResult}; 12 | 13 | use crate::{ 14 | conn::{queryable::AsStatement, ConnMut}, 15 | from_row, from_row_opt, 16 | prelude::FromRow, 17 | Binary, Error, Params, QueryResult, Result, Text, 18 | }; 19 | 20 | /// MySql text query. 21 | /// 22 | /// This trait covers the set of `query*` methods on the `Queryable` trait. 23 | /// Please see the corresponding section of the crate level docs for details. 24 | /// 25 | /// Example: 26 | /// 27 | /// ```rust 28 | /// # mysql::doctest_wrapper!(__result, { 29 | /// use mysql::*; 30 | /// use mysql::prelude::*; 31 | /// let pool = Pool::new(get_opts())?; 32 | /// 33 | /// let num: Option = "SELECT 42".first(&pool)?; 34 | /// 35 | /// assert_eq!(num, Some(42)); 36 | /// # }); 37 | /// ``` 38 | pub trait TextQuery: Sized { 39 | /// This methods corresponds to `Queryable::query_iter`. 40 | fn run<'a, 'b, 'c, C>(self, conn: C) -> Result> 41 | where 42 | C: TryInto>, 43 | Error: From<>>::Error>; 44 | 45 | /// This methods corresponds to `Queryable::query_first`. 46 | fn first<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result> 47 | where 48 | C: TryInto>, 49 | Error: From<>>::Error>, 50 | T: FromRow, 51 | { 52 | self.run(conn)? 53 | .next() 54 | .map(|row| row.map(from_row)) 55 | .transpose() 56 | } 57 | 58 | /// Same as [`TextQuery::first`] but useful when you not sure what your schema is. 59 | fn first_opt<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result>> 60 | where 61 | C: TryInto>, 62 | Error: From<>>::Error>, 63 | T: FromRow, 64 | { 65 | self.run(conn)? 66 | .next() 67 | .map(|row| row.map(from_row_opt)) 68 | .transpose() 69 | } 70 | 71 | /// This methods corresponds to `Queryable::query`. 72 | fn fetch<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result> 73 | where 74 | C: TryInto>, 75 | Error: From<>>::Error>, 76 | T: FromRow, 77 | { 78 | self.run(conn)?.map(|rrow| rrow.map(from_row)).collect() 79 | } 80 | 81 | /// Same as [`TextQuery::fetch`] but useful when you not sure what your schema is. 82 | fn fetch_opt<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result>> 83 | where 84 | C: TryInto>, 85 | Error: From<>>::Error>, 86 | T: FromRow, 87 | { 88 | self.run(conn)?.map(|rrow| rrow.map(from_row_opt)).collect() 89 | } 90 | 91 | /// This methods corresponds to `Queryable::query_fold`. 92 | fn fold<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut init: U, mut next: F) -> Result 93 | where 94 | C: TryInto>, 95 | Error: From<>>::Error>, 96 | T: FromRow, 97 | F: FnMut(U, T) -> U, 98 | { 99 | for row in self.run(conn)? { 100 | init = next(init, from_row(row?)); 101 | } 102 | 103 | Ok(init) 104 | } 105 | 106 | /// Same as [`TextQuery::fold`] but useful when you not sure what your schema is. 107 | fn fold_opt<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut init: U, mut next: F) -> Result 108 | where 109 | C: TryInto>, 110 | Error: From<>>::Error>, 111 | T: FromRow, 112 | F: FnMut(U, StdResult) -> U, 113 | { 114 | for row in self.run(conn)? { 115 | init = next(init, from_row_opt(row?)); 116 | } 117 | 118 | Ok(init) 119 | } 120 | 121 | /// This methods corresponds to `Queryable::query_map`. 122 | fn map<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut map: F) -> Result> 123 | where 124 | C: TryInto>, 125 | Error: From<>>::Error>, 126 | T: FromRow, 127 | F: FnMut(T) -> U, 128 | { 129 | self.fold(conn, Vec::new(), |mut acc, row: T| { 130 | acc.push(map(row)); 131 | acc 132 | }) 133 | } 134 | 135 | /// Same as [`TextQuery::map`] but useful when you not sure what your schema is. 136 | fn map_opt<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut map: F) -> Result> 137 | where 138 | C: TryInto>, 139 | Error: From<>>::Error>, 140 | T: FromRow, 141 | F: FnMut(StdResult) -> U, 142 | { 143 | self.fold_opt( 144 | conn, 145 | Vec::new(), 146 | |mut acc, row: StdResult| { 147 | acc.push(map(row)); 148 | acc 149 | }, 150 | ) 151 | } 152 | } 153 | 154 | impl> TextQuery for Q { 155 | fn run<'a, 'b, 'c, C>(self, conn: C) -> Result> 156 | where 157 | C: TryInto>, 158 | Error: From<>>::Error>, 159 | { 160 | let mut conn = conn.try_into()?; 161 | let meta = conn._query(self.as_ref())?; 162 | Ok(QueryResult::new(conn, meta)) 163 | } 164 | } 165 | 166 | /// Representation of a prepared statement query. 167 | /// 168 | /// See `BinQuery` for details. 169 | #[derive(Debug, Clone, PartialEq, Eq)] 170 | pub struct QueryWithParams { 171 | pub query: Q, 172 | pub params: P, 173 | } 174 | 175 | /// Helper, that constructs `QueryWithParams`. 176 | pub trait WithParams: Sized { 177 | fn with

(self, params: P) -> QueryWithParams; 178 | } 179 | 180 | impl> WithParams for T { 181 | fn with

(self, params: P) -> QueryWithParams { 182 | QueryWithParams { 183 | query: self, 184 | params, 185 | } 186 | } 187 | } 188 | 189 | /// MySql prepared statement query. 190 | /// 191 | /// This trait covers the set of `exec*` methods on the `Queryable` trait. 192 | /// Please see the corresponding section of the crate level docs for details. 193 | /// 194 | /// Example: 195 | /// 196 | /// ```rust 197 | /// # mysql::doctest_wrapper!(__result, { 198 | /// use mysql::*; 199 | /// use mysql::prelude::*; 200 | /// let pool = Pool::new(get_opts())?; 201 | /// 202 | /// let num: Option = "SELECT ?" 203 | /// .with((42,)) 204 | /// .first(&pool)?; 205 | /// 206 | /// assert_eq!(num, Some(42)); 207 | /// # }); 208 | /// ``` 209 | pub trait BinQuery: Sized { 210 | /// This methods corresponds to `Queryable::exec_iter`. 211 | fn run<'a, 'b, 'c, C>(self, conn: C) -> Result> 212 | where 213 | C: TryInto>, 214 | Error: From<>>::Error>; 215 | 216 | /// This methods corresponds to `Queryable::exec_first`. 217 | fn first<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result> 218 | where 219 | C: TryInto>, 220 | Error: From<>>::Error>, 221 | T: FromRow, 222 | { 223 | self.run(conn)? 224 | .next() 225 | .map(|row| row.map(from_row)) 226 | .transpose() 227 | } 228 | 229 | /// Same as [`BinQuery::first`] but useful when you not sure what your schema is. 230 | fn first_opt<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result>> 231 | where 232 | C: TryInto>, 233 | Error: From<>>::Error>, 234 | T: FromRow, 235 | { 236 | self.run(conn)? 237 | .next() 238 | .map(|row| row.map(from_row_opt)) 239 | .transpose() 240 | } 241 | 242 | /// This methods corresponds to `Queryable::exec`. 243 | fn fetch<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result> 244 | where 245 | C: TryInto>, 246 | Error: From<>>::Error>, 247 | T: FromRow, 248 | { 249 | self.run(conn)?.map(|rrow| rrow.map(from_row)).collect() 250 | } 251 | 252 | /// Same as [`BinQuery::fetch`] but useful when you not sure what your schema is. 253 | fn fetch_opt<'a, 'b, 'c: 'b, T, C>(self, conn: C) -> Result>> 254 | where 255 | C: TryInto>, 256 | Error: From<>>::Error>, 257 | T: FromRow, 258 | { 259 | self.run(conn)?.map(|rrow| rrow.map(from_row_opt)).collect() 260 | } 261 | 262 | /// This methods corresponds to `Queryable::exec_fold`. 263 | fn fold<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut init: U, mut next: F) -> Result 264 | where 265 | C: TryInto>, 266 | Error: From<>>::Error>, 267 | T: FromRow, 268 | F: FnMut(U, T) -> U, 269 | { 270 | for row in self.run(conn)? { 271 | init = next(init, from_row(row?)); 272 | } 273 | 274 | Ok(init) 275 | } 276 | 277 | /// Same as [`BinQuery::fold`] but useful when you not sure what your schema is. 278 | fn fold_opt<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut init: U, mut next: F) -> Result 279 | where 280 | C: TryInto>, 281 | Error: From<>>::Error>, 282 | T: FromRow, 283 | F: FnMut(U, StdResult) -> U, 284 | { 285 | for row in self.run(conn)? { 286 | init = next(init, from_row_opt(row?)); 287 | } 288 | 289 | Ok(init) 290 | } 291 | 292 | /// This methods corresponds to `Queryable::exec_map`. 293 | fn map<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut map: F) -> Result> 294 | where 295 | C: TryInto>, 296 | Error: From<>>::Error>, 297 | T: FromRow, 298 | F: FnMut(T) -> U, 299 | { 300 | self.fold(conn, Vec::new(), |mut acc, row: T| { 301 | acc.push(map(row)); 302 | acc 303 | }) 304 | } 305 | 306 | /// Same as [`BinQuery::map`] but useful when you not sure what your schema is. 307 | fn map_opt<'a, 'b, 'c: 'b, T, U, F, C>(self, conn: C, mut map: F) -> Result> 308 | where 309 | C: TryInto>, 310 | Error: From<>>::Error>, 311 | T: FromRow, 312 | F: FnMut(StdResult) -> U, 313 | { 314 | self.fold_opt( 315 | conn, 316 | Vec::new(), 317 | |mut acc, row: StdResult| { 318 | acc.push(map(row)); 319 | acc 320 | }, 321 | ) 322 | } 323 | } 324 | 325 | impl BinQuery for QueryWithParams 326 | where 327 | Q: AsStatement, 328 | P: Into, 329 | { 330 | fn run<'a, 'b, 'c, C>(self, conn: C) -> Result> 331 | where 332 | C: TryInto>, 333 | Error: From<>>::Error>, 334 | { 335 | let mut conn = conn.try_into()?; 336 | let statement = self.query.as_statement(&mut *conn)?; 337 | let meta = conn._execute(&statement, self.params.into())?; 338 | Ok(QueryResult::new(conn, meta)) 339 | } 340 | } 341 | 342 | /// Helper trait for batch statement execution. 343 | /// 344 | /// This trait covers the `Queryable::exec_batch` method. 345 | /// Please see the corresponding section of the crate level docs for details. 346 | /// 347 | /// Example: 348 | /// 349 | /// ```rust 350 | /// # mysql::doctest_wrapper!(__result, { 351 | /// use mysql::*; 352 | /// use mysql::prelude::*; 353 | /// let pool = Pool::new(get_opts())?; 354 | /// 355 | /// // This will prepare `DO ?` and execute `DO 0`, `DO 1`, `DO 2` and so on. 356 | /// "DO ?" 357 | /// .with((0..10).map(|x| (x,))) 358 | /// .batch(&pool)?; 359 | /// # }); 360 | /// ``` 361 | pub trait BatchQuery { 362 | fn batch<'a, 'b, 'c: 'b, C>(self, conn: C) -> Result<()> 363 | where 364 | C: TryInto>, 365 | Error: From<>>::Error>; 366 | } 367 | 368 | impl BatchQuery for QueryWithParams 369 | where 370 | Q: AsStatement, 371 | I: IntoIterator, 372 | P: Into, 373 | { 374 | /// This methods corresponds to `Queryable::exec_batch`. 375 | fn batch<'a, 'b, 'c: 'b, C>(self, conn: C) -> Result<()> 376 | where 377 | C: TryInto>, 378 | Error: From<>>::Error>, 379 | { 380 | let mut conn = conn.try_into()?; 381 | let statement = self.query.as_statement(&mut *conn)?; 382 | 383 | for params in self.params { 384 | let params = params.into(); 385 | let meta = conn._execute(&statement, params)?; 386 | let mut query_result = QueryResult::::new((&mut *conn).into(), meta); 387 | while let Some(result_set) = query_result.iter() { 388 | for row in result_set { 389 | row?; 390 | } 391 | } 392 | } 393 | 394 | Ok(()) 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/conn/query_result.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | pub use mysql_common::proto::{Binary, Text}; 10 | 11 | use mysql_common::{io::ParseBuf, packets::OkPacket, row::RowDeserializer, value::ServerSide}; 12 | 13 | use std::{borrow::Cow, marker::PhantomData, sync::Arc}; 14 | 15 | use crate::{conn::ConnMut, Column, Conn, Error, Result, Row}; 16 | 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 18 | pub enum Or { 19 | A(A), 20 | B(B), 21 | } 22 | 23 | /// Result set kind. 24 | pub trait Protocol: 'static + Send + Sync { 25 | fn next(conn: &mut Conn, columns: Arc<[Column]>) -> Result>; 26 | } 27 | 28 | impl Protocol for Text { 29 | fn next(conn: &mut Conn, columns: Arc<[Column]>) -> Result> { 30 | match conn.next_row_packet()? { 31 | Some(pld) => { 32 | let row = ParseBuf(&pld).parse::>(columns)?; 33 | Ok(Some(row.into())) 34 | } 35 | None => Ok(None), 36 | } 37 | } 38 | } 39 | 40 | impl Protocol for Binary { 41 | fn next(conn: &mut Conn, columns: Arc<[Column]>) -> Result> { 42 | match conn.next_row_packet()? { 43 | Some(pld) => { 44 | let row = ParseBuf(&pld).parse::>(columns)?; 45 | Ok(Some(row.into())) 46 | } 47 | None => Ok(None), 48 | } 49 | } 50 | } 51 | 52 | /// State of a result set iterator. 53 | #[derive(Debug)] 54 | enum SetIteratorState { 55 | /// Iterator is in a non-empty set. 56 | InSet(Arc<[Column]>), 57 | /// Iterator is in an empty set. 58 | InEmptySet(OkPacket<'static>), 59 | /// Iterator is in an errored result set. 60 | Errored(Error), 61 | /// Next result set isn't handled. 62 | OnBoundary, 63 | /// No more result sets. 64 | Done, 65 | } 66 | 67 | impl SetIteratorState { 68 | fn ok_packet(&self) -> Option<&OkPacket<'_>> { 69 | if let Self::InEmptySet(ref ok) = self { 70 | Some(ok) 71 | } else { 72 | None 73 | } 74 | } 75 | 76 | fn columns(&self) -> Option<&Arc<[Column]>> { 77 | if let Self::InSet(ref cols) = self { 78 | Some(cols) 79 | } else { 80 | None 81 | } 82 | } 83 | } 84 | 85 | impl From> for SetIteratorState { 86 | fn from(columns: Vec) -> Self { 87 | Self::InSet(columns.into()) 88 | } 89 | } 90 | 91 | impl From> for SetIteratorState { 92 | fn from(ok_packet: OkPacket<'static>) -> Self { 93 | Self::InEmptySet(ok_packet) 94 | } 95 | } 96 | 97 | impl From for SetIteratorState { 98 | fn from(err: Error) -> Self { 99 | Self::Errored(err) 100 | } 101 | } 102 | 103 | impl From, OkPacket<'static>>> for SetIteratorState { 104 | fn from(or: Or, OkPacket<'static>>) -> Self { 105 | match or { 106 | Or::A(cols) => Self::from(cols), 107 | Or::B(ok) => Self::from(ok), 108 | } 109 | } 110 | } 111 | 112 | /// Response to a query or statement execution. 113 | /// 114 | /// It is an iterator: 115 | /// * over result sets (via `Self::current_set`) 116 | /// * over rows of a current result set (via `Iterator` impl) 117 | #[derive(Debug)] 118 | pub struct QueryResult<'c, 't, 'tc, T: crate::prelude::Protocol> { 119 | conn: ConnMut<'c, 't, 'tc>, 120 | state: SetIteratorState, 121 | set_index: usize, 122 | protocol: PhantomData, 123 | } 124 | 125 | impl<'c, 't, 'tc, T: crate::prelude::Protocol> QueryResult<'c, 't, 'tc, T> { 126 | fn from_state( 127 | conn: ConnMut<'c, 't, 'tc>, 128 | state: SetIteratorState, 129 | ) -> QueryResult<'c, 't, 'tc, T> { 130 | QueryResult { 131 | conn, 132 | state, 133 | set_index: 0, 134 | protocol: PhantomData, 135 | } 136 | } 137 | 138 | pub(crate) fn new( 139 | conn: ConnMut<'c, 't, 'tc>, 140 | meta: Or, OkPacket<'static>>, 141 | ) -> QueryResult<'c, 't, 'tc, T> { 142 | Self::from_state(conn, meta.into()) 143 | } 144 | 145 | /// Updates state with the next result set, if any. 146 | /// 147 | /// Returns `false` if there is no next result set. 148 | /// 149 | /// **Requires:** `self.state == OnBoundary` 150 | fn handle_next(&mut self) { 151 | debug_assert!( 152 | matches!(self.state, SetIteratorState::OnBoundary), 153 | "self.state != OnBoundary" 154 | ); 155 | 156 | if self.conn.more_results_exists() { 157 | match self.conn.handle_result_set() { 158 | Ok(meta) => self.state = meta.into(), 159 | Err(err) => self.state = err.into(), 160 | } 161 | self.set_index += 1; 162 | } else { 163 | self.state = SetIteratorState::Done; 164 | } 165 | } 166 | 167 | /// Returns an iterator over the current result set. 168 | #[deprecated = "Please use QueryResult::iter"] 169 | pub fn next_set<'d>(&'d mut self) -> Option> { 170 | self.iter() 171 | } 172 | 173 | /// Returns an iterator over the current result set. 174 | /// 175 | /// The returned iterator will be consumed either by the caller 176 | /// or implicitly by the `ResultSet::drop`. This operation 177 | /// will advance `self` to the next result set (if any). 178 | /// 179 | /// The following code describes the behavior: 180 | /// 181 | /// ```rust 182 | /// # mysql::doctest_wrapper!(__result, { 183 | /// # use mysql::*; 184 | /// # use mysql::prelude::*; 185 | /// # let pool = Pool::new(get_opts())?; 186 | /// # let mut conn = pool.get_conn()?; 187 | /// # conn.query_drop("CREATE TEMPORARY TABLE mysql.tbl(id INT NOT NULL PRIMARY KEY)")?; 188 | /// 189 | /// let mut query_result = conn.query_iter("\ 190 | /// INSERT INTO mysql.tbl (id) VALUES (3, 4);\ 191 | /// SELECT * FROM mysql.tbl; 192 | /// UPDATE mysql.tbl SET id = id + 1;")?; 193 | /// 194 | /// // query_result is on the first result set at the moment 195 | /// { 196 | /// assert_eq!(query_result.affected_rows(), 2); 197 | /// assert_eq!(query_result.last_insert_id(), Some(4)); 198 | /// 199 | /// let first_result_set = query_result.iter().unwrap(); 200 | /// assert_eq!(first_result_set.affected_rows(), 2); 201 | /// assert_eq!(first_result_set.last_insert_id(), Some(4)); 202 | /// } 203 | /// 204 | /// // the first result set is now dropped, so query_result is on the second result set 205 | /// { 206 | /// assert_eq!(query_result.affected_rows(), 0); 207 | /// assert_eq!(query_result.last_insert_id(), None); 208 | /// 209 | /// let mut second_result_set = query_result.iter().unwrap(); 210 | /// 211 | /// let first_row = second_result_set.next().unwrap().unwrap(); 212 | /// assert_eq!(from_row::(first_row), 3_u8); 213 | /// let second_row = second_result_set.next().unwrap().unwrap(); 214 | /// assert_eq!(from_row::(second_row), 4_u8); 215 | /// 216 | /// assert!(second_result_set.next().is_none()); 217 | /// 218 | /// // second_result_set is consumed but still represents the second result set 219 | /// assert_eq!(second_result_set.affected_rows(), 0); 220 | /// } 221 | /// 222 | /// // the second result set is now dropped, so query_result is on the third result set 223 | /// assert_eq!(query_result.affected_rows(), 2); 224 | /// 225 | /// // QueryResult::drop simply does the following: 226 | /// while query_result.iter().is_some() {} 227 | /// # }); 228 | /// ``` 229 | pub fn iter<'d>(&'d mut self) -> Option> { 230 | use SetIteratorState::*; 231 | 232 | if let OnBoundary | Done = &self.state { 233 | debug_assert!( 234 | !self.conn.more_results_exists(), 235 | "the next state must be handled by the Iterator::next" 236 | ); 237 | 238 | None 239 | } else { 240 | Some(ResultSet { 241 | set_index: self.set_index, 242 | inner: self, 243 | }) 244 | } 245 | } 246 | 247 | /// Returns the number of affected rows for the current result set. 248 | pub fn affected_rows(&self) -> u64 { 249 | self.state 250 | .ok_packet() 251 | .map(|ok| ok.affected_rows()) 252 | .unwrap_or_default() 253 | } 254 | 255 | /// Returns the last insert id for the current result set. 256 | pub fn last_insert_id(&self) -> Option { 257 | self.state 258 | .ok_packet() 259 | .map(|ok| ok.last_insert_id()) 260 | .unwrap_or_default() 261 | } 262 | 263 | /// Returns the warnings count for the current result set. 264 | pub fn warnings(&self) -> u16 { 265 | self.state 266 | .ok_packet() 267 | .map(|ok| ok.warnings()) 268 | .unwrap_or_default() 269 | } 270 | 271 | /// [Info] for the current result set. 272 | /// 273 | /// Will be empty if not defined. 274 | /// 275 | /// [Info]: http://dev.mysql.com/doc/internals/en/packet-OK_Packet.html 276 | pub fn info_ref(&self) -> &[u8] { 277 | self.state 278 | .ok_packet() 279 | .and_then(|ok| ok.info_ref()) 280 | .unwrap_or_default() 281 | } 282 | 283 | /// [Info] for the current result set. 284 | /// 285 | /// Will be empty if not defined. 286 | /// 287 | /// [Info]: http://dev.mysql.com/doc/internals/en/packet-OK_Packet.html 288 | pub fn info_str(&self) -> Cow { 289 | self.state 290 | .ok_packet() 291 | .and_then(|ok| ok.info_str()) 292 | .unwrap_or_else(|| "".into()) 293 | } 294 | 295 | /// Returns columns of the current result rest. 296 | pub fn columns(&self) -> SetColumns { 297 | SetColumns { 298 | inner: self.state.columns().map(Into::into), 299 | } 300 | } 301 | } 302 | 303 | impl<'c, 't, 'tc, T: crate::prelude::Protocol> Drop for QueryResult<'c, 't, 'tc, T> { 304 | fn drop(&mut self) { 305 | while self.iter().is_some() {} 306 | } 307 | } 308 | 309 | #[derive(Debug)] 310 | pub struct ResultSet<'a, 'b, 'c, 'd, T: crate::prelude::Protocol> { 311 | set_index: usize, 312 | inner: &'d mut QueryResult<'a, 'b, 'c, T>, 313 | } 314 | 315 | impl<'a, 'b, 'c, T: crate::prelude::Protocol> std::ops::Deref for ResultSet<'a, 'b, 'c, '_, T> { 316 | type Target = QueryResult<'a, 'b, 'c, T>; 317 | 318 | fn deref(&self) -> &Self::Target { 319 | &*self.inner 320 | } 321 | } 322 | 323 | impl Iterator for ResultSet<'_, '_, '_, '_, T> { 324 | type Item = Result; 325 | 326 | fn next(&mut self) -> Option { 327 | if self.set_index == self.inner.set_index { 328 | self.inner.next() 329 | } else { 330 | None 331 | } 332 | } 333 | } 334 | 335 | impl Iterator for QueryResult<'_, '_, '_, T> { 336 | type Item = Result; 337 | 338 | fn next(&mut self) -> Option { 339 | use SetIteratorState::*; 340 | 341 | let state = std::mem::replace(&mut self.state, OnBoundary); 342 | 343 | match state { 344 | InSet(cols) => match T::next(&mut self.conn, cols.clone()) { 345 | Ok(Some(row)) => { 346 | self.state = InSet(cols); 347 | Some(Ok(row)) 348 | } 349 | Ok(None) => { 350 | self.handle_next(); 351 | None 352 | } 353 | Err(e) => { 354 | self.handle_next(); 355 | Some(Err(e)) 356 | } 357 | }, 358 | InEmptySet(_) => { 359 | self.handle_next(); 360 | None 361 | } 362 | Errored(err) => { 363 | self.handle_next(); 364 | Some(Err(err)) 365 | } 366 | OnBoundary => None, 367 | Done => { 368 | self.state = Done; 369 | None 370 | } 371 | } 372 | } 373 | } 374 | 375 | impl Drop for ResultSet<'_, '_, '_, '_, T> { 376 | fn drop(&mut self) { 377 | while self.next().is_some() {} 378 | } 379 | } 380 | 381 | #[derive(Debug, Clone, PartialEq)] 382 | pub struct SetColumns<'a> { 383 | inner: Option<&'a Arc<[Column]>>, 384 | } 385 | 386 | impl<'a> SetColumns<'a> { 387 | /// Returns an index of a column by its name. 388 | pub fn column_index>(&self, name: U) -> Option { 389 | let name = name.as_ref().as_bytes(); 390 | self.inner 391 | .as_ref() 392 | .and_then(|cols| cols.iter().position(|col| col.name_ref() == name)) 393 | } 394 | } 395 | 396 | impl AsRef<[Column]> for SetColumns<'_> { 397 | fn as_ref(&self) -> &[Column] { 398 | self.inner 399 | .as_ref() 400 | .map(|cols| &(*cols)[..]) 401 | .unwrap_or(&[][..]) 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/conn/queryable.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use mysql_common::row::convert::FromRowError; 10 | 11 | use std::{borrow::Cow, result::Result as StdResult}; 12 | 13 | use crate::{ 14 | conn::query_result::{Binary, Text}, 15 | from_row, from_row_opt, 16 | prelude::FromRow, 17 | Params, QueryResult, Result, Statement, 18 | }; 19 | 20 | /// Something, that eventually is a `Statement` in the context of a `T: Queryable`. 21 | pub trait AsStatement { 22 | /// Make a statement out of `Self`. 23 | fn as_statement(&self, queryable: &mut Q) -> Result>; 24 | } 25 | 26 | /// Queryable object. 27 | pub trait Queryable { 28 | /// Performs text query. 29 | fn query_iter>(&mut self, query: Q) -> Result>; 30 | 31 | /// Performs text query and collects the first result set. 32 | fn query(&mut self, query: Q) -> Result> 33 | where 34 | Q: AsRef, 35 | T: FromRow, 36 | { 37 | self.query_map(query, from_row) 38 | } 39 | 40 | /// Same as [`Queryable::query`] but useful when you not sure what your schema is. 41 | fn query_opt(&mut self, query: Q) -> Result>> 42 | where 43 | Q: AsRef, 44 | T: FromRow, 45 | { 46 | self.query_map(query, from_row_opt) 47 | } 48 | 49 | /// Performs text query and returns the first row of the first result set. 50 | fn query_first(&mut self, query: Q) -> Result> 51 | where 52 | Q: AsRef, 53 | T: FromRow, 54 | { 55 | self.query_iter(query)? 56 | .next() 57 | .map(|row| row.map(from_row)) 58 | .transpose() 59 | } 60 | 61 | /// Same as [`Queryable::query_first`] but useful when you not sure what your schema is. 62 | fn query_first_opt(&mut self, query: Q) -> Result>> 63 | where 64 | Q: AsRef, 65 | T: FromRow, 66 | { 67 | self.query_iter(query)? 68 | .next() 69 | .map(|row| row.map(from_row_opt)) 70 | .transpose() 71 | } 72 | 73 | /// Performs text query and maps each row of the first result set. 74 | fn query_map(&mut self, query: Q, mut f: F) -> Result> 75 | where 76 | Q: AsRef, 77 | T: FromRow, 78 | F: FnMut(T) -> U, 79 | { 80 | self.query_fold(query, Vec::new(), |mut acc, row| { 81 | acc.push(f(row)); 82 | acc 83 | }) 84 | } 85 | 86 | /// Same as [`Queryable::query_map`] but useful when you not sure what your schema is. 87 | fn query_map_opt(&mut self, query: Q, mut f: F) -> Result> 88 | where 89 | Q: AsRef, 90 | T: FromRow, 91 | F: FnMut(StdResult) -> U, 92 | { 93 | self.query_fold_opt(query, Vec::new(), |mut acc, row| { 94 | acc.push(f(row)); 95 | acc 96 | }) 97 | } 98 | 99 | /// Performs text query and folds the first result set to a single value. 100 | fn query_fold(&mut self, query: Q, init: U, mut f: F) -> Result 101 | where 102 | Q: AsRef, 103 | T: FromRow, 104 | F: FnMut(U, T) -> U, 105 | { 106 | self.query_iter(query)? 107 | .map(|row| row.map(from_row::)) 108 | .try_fold(init, |acc, row: Result| row.map(|row| f(acc, row))) 109 | } 110 | 111 | /// Same as [`Queryable::query_fold`] but useful when you not sure what your schema is. 112 | fn query_fold_opt(&mut self, query: Q, init: U, mut f: F) -> Result 113 | where 114 | Q: AsRef, 115 | T: FromRow, 116 | F: FnMut(U, StdResult) -> U, 117 | { 118 | self.query_iter(query)? 119 | .map(|row| row.map(from_row_opt::)) 120 | .try_fold(init, |acc, row: Result>| { 121 | row.map(|row| f(acc, row)) 122 | }) 123 | } 124 | 125 | /// Performs text query and drops the query result. 126 | fn query_drop(&mut self, query: Q) -> Result<()> 127 | where 128 | Q: AsRef, 129 | { 130 | self.query_iter(query).map(drop) 131 | } 132 | 133 | /// Prepares the given `query` as a prepared statement. 134 | fn prep>(&mut self, query: Q) -> Result; 135 | 136 | /// This function will close the given statement on the server side. 137 | fn close(&mut self, stmt: Statement) -> Result<()>; 138 | 139 | /// Executes the given `stmt` with the given `params`. 140 | fn exec_iter(&mut self, stmt: S, params: P) -> Result> 141 | where 142 | S: AsStatement, 143 | P: Into; 144 | 145 | /// Prepares the given statement, and executes it with each item in the given params iterator. 146 | fn exec_batch(&mut self, stmt: S, params: I) -> Result<()> 147 | where 148 | Self: Sized, 149 | S: AsStatement, 150 | P: Into, 151 | I: IntoIterator, 152 | { 153 | let stmt = stmt.as_statement(self)?; 154 | for params in params { 155 | self.exec_drop(stmt.as_ref(), params)?; 156 | } 157 | 158 | Ok(()) 159 | } 160 | 161 | /// Executes the given `stmt` and collects the first result set. 162 | fn exec(&mut self, stmt: S, params: P) -> Result> 163 | where 164 | S: AsStatement, 165 | P: Into, 166 | T: FromRow, 167 | { 168 | self.exec_map(stmt, params, from_row) 169 | } 170 | 171 | /// Same as [`Queryable::exec`] but useful when you not sure what your schema is. 172 | fn exec_opt(&mut self, stmt: S, params: P) -> Result>> 173 | where 174 | S: AsStatement, 175 | P: Into, 176 | T: FromRow, 177 | { 178 | self.exec_map(stmt, params, from_row_opt) 179 | } 180 | 181 | /// Executes the given `stmt` and returns the first row of the first result set. 182 | fn exec_first(&mut self, stmt: S, params: P) -> Result> 183 | where 184 | S: AsStatement, 185 | P: Into, 186 | T: FromRow, 187 | { 188 | self.exec_iter(stmt, params)? 189 | .next() 190 | .map(|row| row.map(crate::from_row)) 191 | .transpose() 192 | } 193 | 194 | /// Same as [`Queryable::exec_first`] but useful when you not sure what your schema is. 195 | fn exec_first_opt( 196 | &mut self, 197 | stmt: S, 198 | params: P, 199 | ) -> Result>> 200 | where 201 | S: AsStatement, 202 | P: Into, 203 | T: FromRow, 204 | { 205 | self.exec_iter(stmt, params)? 206 | .next() 207 | .map(|row| row.map(from_row_opt)) 208 | .transpose() 209 | } 210 | 211 | /// Executes the given `stmt` and maps each row of the first result set. 212 | fn exec_map(&mut self, stmt: S, params: P, mut f: F) -> Result> 213 | where 214 | S: AsStatement, 215 | P: Into, 216 | T: FromRow, 217 | F: FnMut(T) -> U, 218 | { 219 | self.exec_fold(stmt, params, Vec::new(), |mut acc, row| { 220 | acc.push(f(row)); 221 | acc 222 | }) 223 | } 224 | 225 | /// Same as [`Queryable::exec_map`] but useful when you not sure what your schema is. 226 | fn exec_map_opt(&mut self, stmt: S, params: P, mut f: F) -> Result> 227 | where 228 | S: AsStatement, 229 | P: Into, 230 | T: FromRow, 231 | F: FnMut(StdResult) -> U, 232 | { 233 | self.exec_fold_opt(stmt, params, Vec::new(), |mut acc, row| { 234 | acc.push(f(row)); 235 | acc 236 | }) 237 | } 238 | 239 | /// Executes the given `stmt` and folds the first result set to a single value. 240 | fn exec_fold(&mut self, stmt: S, params: P, init: U, mut f: F) -> Result 241 | where 242 | S: AsStatement, 243 | P: Into, 244 | T: FromRow, 245 | F: FnMut(U, T) -> U, 246 | { 247 | let mut result = self.exec_iter(stmt, params)?; 248 | result.try_fold(init, |init, row| row.map(|row| f(init, from_row(row)))) 249 | } 250 | 251 | /// Same as [`Queryable::exec_fold`] but useful when you not sure what your schema is. 252 | fn exec_fold_opt(&mut self, stmt: S, params: P, init: U, mut f: F) -> Result 253 | where 254 | S: AsStatement, 255 | P: Into, 256 | T: FromRow, 257 | F: FnMut(U, StdResult) -> U, 258 | { 259 | let mut result = self.exec_iter(stmt, params)?; 260 | result.try_fold(init, |init, row| row.map(|row| f(init, from_row_opt(row)))) 261 | } 262 | 263 | /// Executes the given `stmt` and drops the result. 264 | fn exec_drop(&mut self, stmt: S, params: P) -> Result<()> 265 | where 266 | S: AsStatement, 267 | P: Into, 268 | { 269 | self.exec_iter(stmt, params).map(drop) 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/conn/stmt.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use mysql_common::{io::ParseBuf, packets::StmtPacket, proto::MyDeserialize}; 10 | 11 | use std::{borrow::Cow, io, sync::Arc}; 12 | 13 | use crate::{prelude::*, Column, Result}; 14 | 15 | #[derive(Debug, Clone, Eq, PartialEq)] 16 | pub struct InnerStmt { 17 | columns: Option>, 18 | params: Option>, 19 | stmt_packet: StmtPacket, 20 | connection_id: u32, 21 | } 22 | 23 | impl<'de> MyDeserialize<'de> for InnerStmt { 24 | const SIZE: Option = StmtPacket::SIZE; 25 | type Ctx = u32; 26 | 27 | fn deserialize(connection_id: Self::Ctx, buf: &mut ParseBuf<'de>) -> io::Result { 28 | let stmt_packet = buf.parse(())?; 29 | 30 | Ok(InnerStmt { 31 | columns: None, 32 | params: None, 33 | stmt_packet, 34 | connection_id, 35 | }) 36 | } 37 | } 38 | 39 | impl InnerStmt { 40 | pub fn with_params(mut self, params: Option>) -> Self { 41 | self.params = params; 42 | self 43 | } 44 | 45 | pub fn with_columns(mut self, columns: Option>) -> Self { 46 | self.columns = columns; 47 | self 48 | } 49 | 50 | pub fn columns(&self) -> &[Column] { 51 | self.columns.as_ref().map(AsRef::as_ref).unwrap_or(&[]) 52 | } 53 | 54 | pub fn params(&self) -> &[Column] { 55 | self.params.as_ref().map(AsRef::as_ref).unwrap_or(&[]) 56 | } 57 | 58 | pub fn id(&self) -> u32 { 59 | self.stmt_packet.statement_id() 60 | } 61 | 62 | pub const fn connection_id(&self) -> u32 { 63 | self.connection_id 64 | } 65 | 66 | pub fn num_params(&self) -> u16 { 67 | self.stmt_packet.num_params() 68 | } 69 | 70 | pub fn num_columns(&self) -> u16 { 71 | self.stmt_packet.num_columns() 72 | } 73 | } 74 | 75 | #[derive(Debug, Clone, Eq, PartialEq)] 76 | pub struct Statement { 77 | pub(crate) inner: Arc, 78 | pub(crate) named_params: Option>>, 79 | } 80 | 81 | impl Statement { 82 | pub(crate) fn new(inner: Arc, named_params: Option>>) -> Self { 83 | Self { 84 | inner, 85 | named_params, 86 | } 87 | } 88 | 89 | pub fn columns(&self) -> &[Column] { 90 | self.inner.columns() 91 | } 92 | 93 | pub fn params(&self) -> &[Column] { 94 | self.inner.params() 95 | } 96 | 97 | pub fn id(&self) -> u32 { 98 | self.inner.id() 99 | } 100 | 101 | pub fn connection_id(&self) -> u32 { 102 | self.inner.connection_id() 103 | } 104 | 105 | pub fn num_params(&self) -> u16 { 106 | self.inner.num_params() 107 | } 108 | 109 | pub fn num_columns(&self) -> u16 { 110 | self.inner.num_columns() 111 | } 112 | } 113 | 114 | impl AsStatement for Statement { 115 | fn as_statement(&self, _queryable: &mut Q) -> Result> { 116 | Ok(Cow::Borrowed(self)) 117 | } 118 | } 119 | 120 | impl<'a> AsStatement for &'a Statement { 121 | fn as_statement(&self, _queryable: &mut Q) -> Result> { 122 | Ok(Cow::Borrowed(self)) 123 | } 124 | } 125 | 126 | impl> AsStatement for T { 127 | fn as_statement(&self, queryable: &mut Q) -> Result> { 128 | let statement = queryable.prep(self.as_ref())?; 129 | Ok(Cow::Owned(statement)) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/conn/stmt_cache.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use lru::LruCache; 10 | use twox_hash::XxHash64; 11 | 12 | use std::{ 13 | borrow::Borrow, 14 | collections::HashMap, 15 | hash::{BuildHasherDefault, Hash}, 16 | sync::Arc, 17 | }; 18 | 19 | use crate::conn::stmt::InnerStmt; 20 | 21 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 22 | pub struct QueryString(pub Arc>); 23 | 24 | impl Borrow<[u8]> for QueryString { 25 | fn borrow(&self) -> &[u8] { 26 | self.0.as_ref() 27 | } 28 | } 29 | 30 | impl PartialEq<[u8]> for QueryString { 31 | fn eq(&self, other: &[u8]) -> bool { 32 | &**self.0.as_ref() == other 33 | } 34 | } 35 | 36 | pub struct Entry { 37 | pub stmt: Arc, 38 | pub query: QueryString, 39 | } 40 | 41 | #[derive(Debug)] 42 | pub struct StmtCache { 43 | cap: usize, 44 | cache: LruCache, 45 | query_map: HashMap>, 46 | } 47 | 48 | impl StmtCache { 49 | pub fn new(cap: usize) -> StmtCache { 50 | StmtCache { 51 | cap, 52 | cache: LruCache::unbounded(), 53 | query_map: Default::default(), 54 | } 55 | } 56 | 57 | pub fn contains_query(&self, key: &T) -> bool 58 | where 59 | QueryString: Borrow, 60 | T: Hash + Eq, 61 | T: ?Sized, 62 | { 63 | self.query_map.contains_key(key) 64 | } 65 | 66 | pub fn by_query(&mut self, query: &T) -> Option<&Entry> 67 | where 68 | QueryString: Borrow, 69 | QueryString: PartialEq, 70 | T: Hash + Eq, 71 | T: ?Sized, 72 | { 73 | let id = self.query_map.get(query).cloned(); 74 | match id { 75 | Some(id) => self.cache.get(&id), 76 | None => None, 77 | } 78 | } 79 | 80 | pub fn put(&mut self, query: Arc>, stmt: Arc) -> Option> { 81 | if self.cap == 0 { 82 | return None; 83 | } 84 | 85 | let query = QueryString(query); 86 | 87 | self.query_map.insert(query.clone(), stmt.id()); 88 | self.cache.put(stmt.id(), Entry { stmt, query }); 89 | 90 | if self.cache.len() > self.cap { 91 | if let Some((_, entry)) = self.cache.pop_lru() { 92 | self.query_map.remove(&**entry.query.0.as_ref()); 93 | return Some(entry.stmt); 94 | } 95 | } 96 | 97 | None 98 | } 99 | 100 | pub fn clear(&mut self) { 101 | self.query_map.clear(); 102 | self.cache.clear(); 103 | } 104 | 105 | pub fn remove(&mut self, id: u32) { 106 | if let Some(entry) = self.cache.pop(&id) { 107 | self.query_map.remove::<[u8]>(entry.query.borrow()); 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | pub fn iter(&self) -> impl Iterator { 113 | self.cache.iter() 114 | } 115 | 116 | pub fn into_iter(mut self) -> impl Iterator { 117 | std::iter::from_fn(move || self.cache.pop_lru()) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/conn/transaction.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use mysql_common::packets::OkPacket; 10 | 11 | use std::{borrow::Cow, fmt}; 12 | 13 | use crate::{ 14 | conn::{ 15 | query_result::{Binary, Text}, 16 | ConnMut, 17 | }, 18 | prelude::*, 19 | LocalInfileHandler, Params, QueryResult, Result, Statement, 20 | }; 21 | 22 | /// MySql transaction options. 23 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] 24 | pub struct TxOpts { 25 | with_consistent_snapshot: bool, 26 | isolation_level: Option, 27 | access_mode: Option, 28 | } 29 | 30 | impl TxOpts { 31 | /// Returns the value of the characteristic. 32 | pub fn with_consistent_snapshot(&self) -> bool { 33 | self.with_consistent_snapshot 34 | } 35 | 36 | /// Returns the access mode value. 37 | pub fn access_mode(&self) -> Option { 38 | self.access_mode 39 | } 40 | 41 | /// Returns the isolation level value. 42 | pub fn isolation_level(&self) -> Option { 43 | self.isolation_level 44 | } 45 | 46 | /// Turns on/off the `WITH CONSISTENT SNAPSHOT` tx characteristic (defaults to `false`). 47 | pub fn set_with_consistent_snapshot(mut self, val: bool) -> Self { 48 | self.with_consistent_snapshot = val; 49 | self 50 | } 51 | 52 | /// Defines the transaction access mode (defaults to `None`, i.e unspecified). 53 | pub fn set_access_mode(mut self, access_mode: Option) -> Self { 54 | self.access_mode = access_mode; 55 | self 56 | } 57 | 58 | /// Defines the transaction isolation level (defaults to `None`, i.e. unspecified). 59 | pub fn set_isolation_level(mut self, level: Option) -> Self { 60 | self.isolation_level = level; 61 | self 62 | } 63 | } 64 | 65 | /// MySql transaction access mode. 66 | #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] 67 | #[repr(u8)] 68 | pub enum AccessMode { 69 | ReadOnly, 70 | ReadWrite, 71 | } 72 | 73 | /// MySql transaction isolation level. 74 | #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)] 75 | #[repr(u8)] 76 | pub enum IsolationLevel { 77 | ReadUncommitted, 78 | ReadCommitted, 79 | RepeatableRead, 80 | Serializable, 81 | } 82 | 83 | impl fmt::Display for IsolationLevel { 84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | match *self { 86 | IsolationLevel::ReadUncommitted => write!(f, "READ UNCOMMITTED"), 87 | IsolationLevel::ReadCommitted => write!(f, "READ COMMITTED"), 88 | IsolationLevel::RepeatableRead => write!(f, "REPEATABLE READ"), 89 | IsolationLevel::Serializable => write!(f, "SERIALIZABLE"), 90 | } 91 | } 92 | } 93 | 94 | #[derive(Debug)] 95 | pub struct Transaction<'a> { 96 | pub(crate) conn: ConnMut<'a, 'static, 'static>, 97 | committed: bool, 98 | rolled_back: bool, 99 | restore_local_infile_handler: Option, 100 | } 101 | 102 | impl Transaction<'_> { 103 | pub(crate) fn new<'a>(conn: ConnMut<'a, 'static, 'static>) -> Transaction<'a> { 104 | let handler = conn.0.local_infile_handler.clone(); 105 | Transaction { 106 | conn, 107 | committed: false, 108 | rolled_back: false, 109 | restore_local_infile_handler: handler, 110 | } 111 | } 112 | 113 | /// Will consume and commit transaction. 114 | pub fn commit(mut self) -> Result<()> { 115 | self.conn.query_drop("COMMIT")?; 116 | self.committed = true; 117 | Ok(()) 118 | } 119 | 120 | /// Will consume and rollback transaction. You also can rely on `Drop` implementation but it 121 | /// will swallow errors. 122 | pub fn rollback(mut self) -> Result<()> { 123 | self.conn.query_drop("ROLLBACK")?; 124 | self.rolled_back = true; 125 | Ok(()) 126 | } 127 | 128 | /// A way to override local infile handler for this transaction. 129 | /// Destructor of transaction will restore original handler. 130 | pub fn set_local_infile_handler(&mut self, handler: Option) { 131 | self.conn.set_local_infile_handler(handler); 132 | } 133 | 134 | /// Returns the number of affected rows, reported by the server. 135 | pub fn affected_rows(&self) -> u64 { 136 | self.conn.affected_rows() 137 | } 138 | 139 | /// Returns the last insert id of the last query, if any. 140 | pub fn last_insert_id(&self) -> Option { 141 | self.conn 142 | .0 143 | .ok_packet 144 | .as_ref() 145 | .and_then(OkPacket::last_insert_id) 146 | } 147 | 148 | /// Returns the warnings count, reported by the server. 149 | pub fn warnings(&self) -> u16 { 150 | self.conn.warnings() 151 | } 152 | 153 | /// [Info], reported by the server. 154 | /// 155 | /// Will be empty if not defined. 156 | /// 157 | /// [Info]: http://dev.mysql.com/doc/internals/en/packet-OK_Packet.html 158 | pub fn info_ref(&self) -> &[u8] { 159 | self.conn.info_ref() 160 | } 161 | 162 | /// [Info], reported by the server. 163 | /// 164 | /// Will be empty if not defined. 165 | /// 166 | /// [Info]: http://dev.mysql.com/doc/internals/en/packet-OK_Packet.html 167 | pub fn info_str(&self) -> Cow { 168 | self.conn.info_str() 169 | } 170 | } 171 | 172 | impl<'a> Queryable for Transaction<'a> { 173 | fn query_iter>(&mut self, query: T) -> Result> { 174 | self.conn.query_iter(query) 175 | } 176 | 177 | fn prep>(&mut self, query: T) -> Result { 178 | self.conn.prep(query) 179 | } 180 | 181 | fn close(&mut self, stmt: Statement) -> Result<()> { 182 | self.conn.close(stmt) 183 | } 184 | 185 | fn exec_iter(&mut self, stmt: S, params: P) -> Result> 186 | where 187 | S: AsStatement, 188 | P: Into, 189 | { 190 | self.conn.exec_iter(stmt, params) 191 | } 192 | } 193 | 194 | impl<'a> Drop for Transaction<'a> { 195 | /// Will rollback transaction. 196 | fn drop(&mut self) { 197 | if !self.committed && !self.rolled_back { 198 | let _ = self.conn.query_drop("ROLLBACK"); 199 | } 200 | self.conn.0.local_infile_handler = self.restore_local_infile_handler.take(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/error/tls/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(any(feature = "native-tls", feature = "rustls"))] 2 | 3 | mod native_tls_error; 4 | mod rustls_error; 5 | 6 | #[cfg(feature = "native-tls")] 7 | #[cfg_attr( 8 | docsrs, 9 | doc(cfg(any( 10 | feature = "native-tls", 11 | feature = "rustls-tls", 12 | feature = "rustls-tls-ring" 13 | ))) 14 | )] 15 | pub use native_tls_error::TlsError; 16 | 17 | #[cfg(feature = "rustls")] 18 | #[cfg_attr( 19 | docsrs, 20 | doc(cfg(any( 21 | feature = "native-tls", 22 | feature = "rustls-tls", 23 | feature = "rustls-tls-ring" 24 | ))) 25 | )] 26 | pub use rustls_error::TlsError; 27 | -------------------------------------------------------------------------------- /src/error/tls/native_tls_error.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "native-tls")] 2 | 3 | use std::fmt::Display; 4 | 5 | #[derive(Debug)] 6 | pub enum TlsError { 7 | TlsError(native_tls::Error), 8 | TlsHandshakeError(native_tls::HandshakeError), 9 | } 10 | 11 | impl From for super::super::Error { 12 | fn from(err: TlsError) -> super::super::Error { 13 | super::super::Error::TlsError(err) 14 | } 15 | } 16 | 17 | impl From for super::super::Error { 18 | fn from(err: native_tls::Error) -> super::super::Error { 19 | super::super::Error::TlsError(TlsError::TlsError(err)) 20 | } 21 | } 22 | 23 | impl From> for super::super::Error { 24 | fn from(err: native_tls::HandshakeError) -> super::super::Error { 25 | super::super::Error::TlsError(TlsError::TlsHandshakeError(err)) 26 | } 27 | } 28 | 29 | impl std::error::Error for TlsError { 30 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 31 | match self { 32 | TlsError::TlsError(e) => Some(e), 33 | TlsError::TlsHandshakeError(e) => Some(e), 34 | } 35 | } 36 | } 37 | 38 | impl Display for TlsError { 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 | match self { 41 | TlsError::TlsError(e) => e.fmt(f), 42 | TlsError::TlsHandshakeError(e) => e.fmt(f), 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/error/tls/rustls_error.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "rustls")] 2 | 3 | use std::fmt::Display; 4 | 5 | use rustls::server::VerifierBuilderError; 6 | 7 | #[derive(Debug)] 8 | pub enum TlsError { 9 | VerifierBuilderError(VerifierBuilderError), 10 | Tls(rustls::Error), 11 | Pki(webpki::Error), 12 | InvalidDnsName(webpki::InvalidDnsNameError), 13 | } 14 | 15 | impl From for crate::Error { 16 | fn from(e: TlsError) -> Self { 17 | crate::Error::TlsError(e) 18 | } 19 | } 20 | 21 | impl From for TlsError { 22 | fn from(e: VerifierBuilderError) -> Self { 23 | TlsError::VerifierBuilderError(e) 24 | } 25 | } 26 | 27 | impl From for TlsError { 28 | fn from(e: rustls::Error) -> Self { 29 | TlsError::Tls(e) 30 | } 31 | } 32 | 33 | impl From for TlsError { 34 | fn from(e: webpki::InvalidDnsNameError) -> Self { 35 | TlsError::InvalidDnsName(e) 36 | } 37 | } 38 | 39 | impl From for TlsError { 40 | fn from(e: webpki::Error) -> Self { 41 | TlsError::Pki(e) 42 | } 43 | } 44 | 45 | impl From for crate::Error { 46 | fn from(e: rustls::Error) -> Self { 47 | crate::Error::TlsError(e.into()) 48 | } 49 | } 50 | 51 | impl From for crate::Error { 52 | fn from(e: webpki::Error) -> Self { 53 | crate::Error::TlsError(e.into()) 54 | } 55 | } 56 | 57 | impl From for crate::Error { 58 | fn from(e: webpki::InvalidDnsNameError) -> Self { 59 | crate::Error::TlsError(e.into()) 60 | } 61 | } 62 | 63 | impl std::error::Error for TlsError { 64 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 65 | match self { 66 | TlsError::VerifierBuilderError(e) => Some(e), 67 | TlsError::Tls(e) => Some(e), 68 | TlsError::Pki(e) => Some(e), 69 | TlsError::InvalidDnsName(e) => Some(e), 70 | } 71 | } 72 | } 73 | 74 | impl Display for TlsError { 75 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 76 | match self { 77 | TlsError::VerifierBuilderError(e) => e.fmt(f), 78 | TlsError::Tls(e) => e.fmt(f), 79 | TlsError::Pki(e) => e.fmt(f), 80 | TlsError::InvalidDnsName(e) => e.fmt(f), 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/io/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use bufstream::BufStream; 10 | use io_enum::*; 11 | #[cfg(windows)] 12 | use named_pipe as np; 13 | 14 | #[cfg(unix)] 15 | use std::os::{ 16 | unix, 17 | unix::io::{AsRawFd, RawFd}, 18 | }; 19 | use std::{ 20 | fmt, io, 21 | net::{self, SocketAddr}, 22 | time::Duration, 23 | }; 24 | 25 | use crate::error::{ 26 | DriverError::{ConnectTimeout, CouldNotConnect}, 27 | Error::DriverError, 28 | Result as MyResult, 29 | }; 30 | 31 | mod tcp; 32 | mod tls; 33 | 34 | #[derive(Debug, Read, Write)] 35 | pub enum Stream { 36 | #[cfg(unix)] 37 | SocketStream(BufStream), 38 | #[cfg(windows)] 39 | SocketStream(BufStream), 40 | TcpStream(TcpStream), 41 | } 42 | 43 | impl Stream { 44 | #[cfg(unix)] 45 | pub fn connect_socket( 46 | socket: &str, 47 | read_timeout: Option, 48 | write_timeout: Option, 49 | ) -> MyResult { 50 | match unix::net::UnixStream::connect(socket) { 51 | Ok(stream) => { 52 | stream.set_read_timeout(read_timeout)?; 53 | stream.set_write_timeout(write_timeout)?; 54 | Ok(Stream::SocketStream(BufStream::new(stream))) 55 | } 56 | Err(e) => { 57 | let addr = socket.to_string(); 58 | let desc = e.to_string(); 59 | Err(DriverError(CouldNotConnect(Some((addr, desc, e.kind()))))) 60 | } 61 | } 62 | } 63 | 64 | #[cfg(windows)] 65 | pub fn connect_socket( 66 | socket: &str, 67 | read_timeout: Option, 68 | write_timeout: Option, 69 | ) -> MyResult { 70 | let full_name = format!(r"\\.\pipe\{}", socket); 71 | match np::PipeClient::connect(full_name.clone()) { 72 | Ok(mut stream) => { 73 | stream.set_read_timeout(read_timeout); 74 | stream.set_write_timeout(write_timeout); 75 | Ok(Stream::SocketStream(BufStream::new(stream))) 76 | } 77 | Err(e) => { 78 | let desc = format!("{}", e); 79 | Err(DriverError(CouldNotConnect(Some(( 80 | full_name, 81 | desc, 82 | e.kind(), 83 | ))))) 84 | } 85 | } 86 | } 87 | 88 | #[cfg(all(not(unix), not(windows)))] 89 | fn connect_socket(&mut self) -> MyResult<()> { 90 | unimplemented!("Sockets is not implemented on current platform"); 91 | } 92 | 93 | #[allow(clippy::too_many_arguments)] 94 | pub fn connect_tcp( 95 | ip_or_hostname: &str, 96 | port: u16, 97 | read_timeout: Option, 98 | write_timeout: Option, 99 | tcp_keepalive_time: Option, 100 | #[cfg(any(target_os = "linux", target_os = "macos",))] 101 | tcp_keepalive_probe_interval_secs: Option, 102 | #[cfg(any(target_os = "linux", target_os = "macos",))] tcp_keepalive_probe_count: Option< 103 | u32, 104 | >, 105 | #[cfg(target_os = "linux")] tcp_user_timeout: Option, 106 | nodelay: bool, 107 | tcp_connect_timeout: Option, 108 | bind_address: Option, 109 | ) -> MyResult { 110 | let mut builder = tcp::MyTcpBuilder::new((ip_or_hostname, port)); 111 | builder 112 | .connect_timeout(tcp_connect_timeout) 113 | .read_timeout(read_timeout) 114 | .write_timeout(write_timeout) 115 | .keepalive_time_ms(tcp_keepalive_time) 116 | .nodelay(nodelay) 117 | .bind_address(bind_address); 118 | #[cfg(any(target_os = "linux", target_os = "macos",))] 119 | builder.keepalive_probe_interval_secs(tcp_keepalive_probe_interval_secs); 120 | #[cfg(any(target_os = "linux", target_os = "macos",))] 121 | builder.keepalive_probe_count(tcp_keepalive_probe_count); 122 | #[cfg(target_os = "linux")] 123 | builder.user_timeout(tcp_user_timeout); 124 | builder 125 | .connect() 126 | .map(|stream| Stream::TcpStream(TcpStream::Insecure(BufStream::new(stream)))) 127 | .map_err(|err| { 128 | if err.kind() == io::ErrorKind::TimedOut { 129 | DriverError(ConnectTimeout) 130 | } else { 131 | let addr = format!("{}:{}", ip_or_hostname, port); 132 | let desc = format!("{}", err); 133 | DriverError(CouldNotConnect(Some((addr, desc, err.kind())))) 134 | } 135 | }) 136 | } 137 | 138 | pub fn is_insecure(&self) -> bool { 139 | matches!(self, Stream::TcpStream(TcpStream::Insecure(_))) 140 | } 141 | 142 | pub fn is_socket(&self) -> bool { 143 | matches!(self, Stream::SocketStream(_)) 144 | } 145 | 146 | #[cfg(all(not(feature = "native-tls"), not(feature = "rustls")))] 147 | pub fn make_secure(self, _host: url::Host, _ssl_opts: crate::SslOpts) -> MyResult { 148 | panic!( 149 | "Client had asked for TLS connection but TLS support is disabled. \ 150 | Please enable one of the following features: \"native-tls\", \"rustls-tls\", \"rustls-tls-ring\"" 151 | ) 152 | } 153 | } 154 | 155 | #[cfg(unix)] 156 | impl AsRawFd for Stream { 157 | fn as_raw_fd(&self) -> RawFd { 158 | match self { 159 | Stream::SocketStream(stream) => stream.get_ref().as_raw_fd(), 160 | Stream::TcpStream(stream) => stream.as_raw_fd(), 161 | } 162 | } 163 | } 164 | 165 | #[derive(Read, Write)] 166 | pub enum TcpStream { 167 | #[cfg(feature = "native-tls")] 168 | Secure(BufStream>), 169 | #[cfg(feature = "rustls")] 170 | Secure(BufStream>>), 171 | Insecure(BufStream), 172 | } 173 | 174 | #[cfg(unix)] 175 | impl AsRawFd for TcpStream { 176 | fn as_raw_fd(&self) -> RawFd { 177 | match self { 178 | #[cfg(feature = "native-tls")] 179 | TcpStream::Secure(stream) => stream.get_ref().get_ref().as_raw_fd(), 180 | #[cfg(feature = "rustls")] 181 | TcpStream::Secure(stream) => stream.get_ref().get_ref().as_raw_fd(), 182 | TcpStream::Insecure(stream) => stream.get_ref().as_raw_fd(), 183 | } 184 | } 185 | } 186 | 187 | impl fmt::Debug for TcpStream { 188 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 189 | match *self { 190 | #[cfg(feature = "native-tls")] 191 | TcpStream::Secure(ref s) => write!(f, "Secure stream {:?}", s), 192 | #[cfg(feature = "rustls")] 193 | TcpStream::Secure(ref s) => write!(f, "Secure stream {:?}", s), 194 | TcpStream::Insecure(ref s) => write!(f, "Insecure stream {:?}", s), 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/io/tcp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 rust-mysql-simple contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , 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 | use socket2::{Domain, SockAddr, Socket, Type}; 10 | 11 | use std::{ 12 | io, 13 | net::{SocketAddr, TcpStream, ToSocketAddrs}, 14 | time::Duration, 15 | }; 16 | 17 | pub struct MyTcpBuilder { 18 | address: T, 19 | bind_address: Option, 20 | connect_timeout: Option, 21 | read_timeout: Option, 22 | write_timeout: Option, 23 | keepalive_time_ms: Option, 24 | #[cfg(any(target_os = "linux", target_os = "macos",))] 25 | keepalive_probe_interval_secs: Option, 26 | #[cfg(any(target_os = "linux", target_os = "macos",))] 27 | keepalive_probe_count: Option, 28 | #[cfg(target_os = "linux")] 29 | user_timeout: Option, 30 | nodelay: bool, 31 | } 32 | 33 | impl MyTcpBuilder { 34 | pub fn keepalive_time_ms(&mut self, keepalive_time_ms: Option) -> &mut Self { 35 | self.keepalive_time_ms = keepalive_time_ms; 36 | self 37 | } 38 | 39 | #[cfg(any(target_os = "linux", target_os = "macos",))] 40 | pub fn keepalive_probe_interval_secs( 41 | &mut self, 42 | keepalive_probe_interval_secs: Option, 43 | ) -> &mut Self { 44 | self.keepalive_probe_interval_secs = keepalive_probe_interval_secs; 45 | self 46 | } 47 | 48 | #[cfg(any(target_os = "linux", target_os = "macos",))] 49 | pub fn keepalive_probe_count(&mut self, keepalive_probe_count: Option) -> &mut Self { 50 | self.keepalive_probe_count = keepalive_probe_count; 51 | self 52 | } 53 | 54 | #[cfg(target_os = "linux")] 55 | pub fn user_timeout(&mut self, user_timeout: Option) -> &mut Self { 56 | self.user_timeout = user_timeout; 57 | self 58 | } 59 | 60 | pub fn nodelay(&mut self, nodelay: bool) -> &mut Self { 61 | self.nodelay = nodelay; 62 | self 63 | } 64 | 65 | pub fn write_timeout(&mut self, write_timeout: Option) -> &mut Self { 66 | self.write_timeout = write_timeout; 67 | self 68 | } 69 | 70 | pub fn read_timeout(&mut self, read_timeout: Option) -> &mut Self { 71 | self.read_timeout = read_timeout; 72 | self 73 | } 74 | 75 | pub fn bind_address(&mut self, bind_address: Option) -> &mut Self 76 | where 77 | U: Into, 78 | { 79 | self.bind_address = bind_address.map(Into::into); 80 | self 81 | } 82 | 83 | pub fn connect_timeout(&mut self, timeout: Option) -> &mut Self { 84 | self.connect_timeout = timeout; 85 | self 86 | } 87 | 88 | pub fn new(address: T) -> MyTcpBuilder { 89 | MyTcpBuilder { 90 | address, 91 | bind_address: None, 92 | connect_timeout: None, 93 | read_timeout: None, 94 | write_timeout: None, 95 | keepalive_time_ms: None, 96 | #[cfg(any(target_os = "linux", target_os = "macos",))] 97 | keepalive_probe_interval_secs: None, 98 | #[cfg(any(target_os = "linux", target_os = "macos",))] 99 | keepalive_probe_count: None, 100 | #[cfg(target_os = "linux")] 101 | user_timeout: None, 102 | nodelay: true, 103 | } 104 | } 105 | 106 | pub fn connect(self) -> io::Result { 107 | let MyTcpBuilder { 108 | address, 109 | bind_address, 110 | connect_timeout, 111 | read_timeout, 112 | write_timeout, 113 | keepalive_time_ms, 114 | #[cfg(any(target_os = "linux", target_os = "macos"))] 115 | keepalive_probe_interval_secs, 116 | #[cfg(any(target_os = "linux", target_os = "macos",))] 117 | keepalive_probe_count, 118 | #[cfg(target_os = "linux")] 119 | user_timeout, 120 | nodelay, 121 | } = self; 122 | let err_msg = if bind_address.is_none() { 123 | "could not connect to any address" 124 | } else { 125 | "could not connect to any address with specified bind address" 126 | }; 127 | let err = io::Error::new(io::ErrorKind::Other, err_msg); 128 | 129 | let addrs = address.to_socket_addrs()?.collect::>(); 130 | 131 | let socket = if let Some(bind_address) = bind_address { 132 | let fold_fun = |prev, sock_addr: &SocketAddr| match prev { 133 | Ok(socket) => Ok(socket), 134 | Err(_) => { 135 | let domain = Domain::for_address(*sock_addr); 136 | let socket = Socket::new(domain, Type::STREAM, None)?; 137 | socket.bind(&bind_address.into())?; 138 | if let Some(connect_timeout) = connect_timeout { 139 | socket.connect_timeout(&SockAddr::from(*sock_addr), connect_timeout)?; 140 | } else { 141 | socket.connect(&SockAddr::from(*sock_addr))?; 142 | } 143 | Ok(socket) 144 | } 145 | }; 146 | 147 | if bind_address.is_ipv4() { 148 | // client wants to bind to ipv4, so let's look for ipv4 addresses first 149 | addrs 150 | .iter() 151 | .filter(|x| x.is_ipv4()) 152 | .fold(Err(err), fold_fun) 153 | .or_else(|e| addrs.iter().filter(|x| x.is_ipv6()).fold(Err(e), fold_fun)) 154 | } else { 155 | // client wants to bind to ipv6, so let's look for ipv6 addresses first 156 | addrs 157 | .iter() 158 | .filter(|x| x.is_ipv6()) 159 | .fold(Err(err), fold_fun) 160 | .or_else(|e| addrs.iter().filter(|x| x.is_ipv4()).fold(Err(e), fold_fun)) 161 | } 162 | } else { 163 | // no bind address 164 | addrs 165 | .into_iter() 166 | .try_fold(None, |prev, sock_addr| match prev { 167 | Some(x) => io::Result::Ok(Some(x)), 168 | None => { 169 | let domain = Domain::for_address(sock_addr); 170 | let socket = Socket::new(domain, Type::STREAM, None)?; 171 | if let Some(connect_timeout) = connect_timeout { 172 | socket.connect_timeout(&sock_addr.into(), connect_timeout)?; 173 | } else { 174 | socket.connect(&sock_addr.into())?; 175 | } 176 | Ok(Some(socket)) 177 | } 178 | })? 179 | .ok_or(err) 180 | }?; 181 | 182 | socket.set_read_timeout(read_timeout)?; 183 | socket.set_write_timeout(write_timeout)?; 184 | if let Some(duration) = keepalive_time_ms { 185 | let conf = 186 | socket2::TcpKeepalive::new().with_time(Duration::from_millis(duration as u64)); 187 | socket.set_tcp_keepalive(&conf)?; 188 | } 189 | #[cfg(any(target_os = "linux", target_os = "macos",))] 190 | if let Some(keepalive_probe_interval_secs) = keepalive_probe_interval_secs { 191 | use std::os::unix::io::AsRawFd; 192 | let fd = socket.as_raw_fd(); 193 | unsafe { 194 | if libc::setsockopt( 195 | fd, 196 | libc::IPPROTO_TCP, 197 | libc::TCP_KEEPINTVL, 198 | &keepalive_probe_interval_secs as *const _ as *const libc::c_void, 199 | std::mem::size_of_val(&keepalive_probe_interval_secs) as libc::socklen_t, 200 | ) != 0 201 | { 202 | return Err(io::Error::last_os_error()); 203 | } 204 | } 205 | } 206 | #[cfg(any(target_os = "linux", target_os = "macos"))] 207 | if let Some(keepalive_probe_count) = keepalive_probe_count { 208 | use std::os::unix::io::AsRawFd; 209 | let fd = socket.as_raw_fd(); 210 | unsafe { 211 | if libc::setsockopt( 212 | fd, 213 | libc::IPPROTO_TCP, 214 | libc::TCP_KEEPCNT, 215 | &keepalive_probe_count as *const _ as *const libc::c_void, 216 | std::mem::size_of_val(&keepalive_probe_count) as libc::socklen_t, 217 | ) != 0 218 | { 219 | return Err(io::Error::last_os_error()); 220 | } 221 | } 222 | } 223 | #[cfg(target_os = "linux")] 224 | if let Some(timeout) = user_timeout { 225 | use std::os::unix::io::AsRawFd; 226 | let fd = socket.as_raw_fd(); 227 | unsafe { 228 | if libc::setsockopt( 229 | fd, 230 | libc::SOL_TCP, 231 | libc::TCP_USER_TIMEOUT, 232 | &timeout as *const _ as *const libc::c_void, 233 | std::mem::size_of_val(&timeout) as libc::socklen_t, 234 | ) != 0 235 | { 236 | return Err(io::Error::last_os_error()); 237 | } 238 | } 239 | } 240 | socket.set_nodelay(nodelay)?; 241 | Ok(TcpStream::from(socket)) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/io/tls/mod.rs: -------------------------------------------------------------------------------- 1 | #![cfg(any(feature = "native-tls", feature = "rustls"))] 2 | 3 | mod native_tls_io; 4 | mod rustls_io; 5 | -------------------------------------------------------------------------------- /src/io/tls/native_tls_io.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "native-tls")] 2 | 3 | use std::{ 4 | fs::File, 5 | io::{self, Read}, 6 | }; 7 | 8 | use bufstream::BufStream; 9 | use native_tls::{Certificate, TlsConnector}; 10 | 11 | use crate::{ 12 | io::{Stream, TcpStream}, 13 | Result, SslOpts, 14 | }; 15 | 16 | impl Stream { 17 | pub fn make_secure(self, host: url::Host, ssl_opts: SslOpts) -> Result { 18 | if self.is_socket() { 19 | // won't secure socket connection 20 | return Ok(self); 21 | } 22 | 23 | let domain = match host { 24 | url::Host::Domain(domain) => domain, 25 | url::Host::Ipv4(ip) => ip.to_string(), 26 | url::Host::Ipv6(ip) => ip.to_string(), 27 | }; 28 | 29 | let mut builder = TlsConnector::builder(); 30 | if let Some(root_cert_path) = ssl_opts.root_cert_path() { 31 | let mut root_cert_data = vec![]; 32 | let mut root_cert_file = File::open(root_cert_path)?; 33 | root_cert_file.read_to_end(&mut root_cert_data)?; 34 | 35 | let root_certs = Certificate::from_der(&root_cert_data) 36 | .map(|x| vec![x]) 37 | .or_else(|_| { 38 | pem::parse_many(&*root_cert_data) 39 | .unwrap_or_default() 40 | .iter() 41 | .map(pem::encode) 42 | .map(|s| Certificate::from_pem(s.as_bytes())) 43 | .collect() 44 | })?; 45 | 46 | for root_cert in root_certs { 47 | builder.add_root_certificate(root_cert); 48 | } 49 | } 50 | if let Some(client_identity) = ssl_opts.client_identity() { 51 | let identity = client_identity.load()?; 52 | builder.identity(identity); 53 | } 54 | builder.danger_accept_invalid_hostnames(ssl_opts.skip_domain_validation()); 55 | builder.danger_accept_invalid_certs(ssl_opts.accept_invalid_certs()); 56 | let tls_connector = builder.build()?; 57 | match self { 58 | Stream::TcpStream(tcp_stream) => match tcp_stream { 59 | TcpStream::Insecure(insecure_stream) => { 60 | let inner = insecure_stream.into_inner().map_err(io::Error::from)?; 61 | let secure_stream = tls_connector.connect(&domain, inner)?; 62 | Ok(Stream::TcpStream(TcpStream::Secure(BufStream::new( 63 | secure_stream, 64 | )))) 65 | } 66 | TcpStream::Secure(_) => Ok(Stream::TcpStream(tcp_stream)), 67 | }, 68 | _ => unreachable!(), 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/io/tls/rustls_io.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "rustls")] 2 | 3 | use std::{ 4 | fs::File, 5 | io::{self, Read}, 6 | sync::Arc, 7 | }; 8 | 9 | use bufstream::BufStream; 10 | use rustls::{ 11 | client::{ 12 | danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, 13 | WebPkiServerVerifier, 14 | }, 15 | pki_types::{CertificateDer, ServerName, UnixTime}, 16 | CertificateError, ClientConfig, Error, RootCertStore, SignatureScheme, 17 | }; 18 | use rustls_pemfile::certs; 19 | 20 | use crate::{ 21 | error::tls::TlsError, 22 | io::{Stream, TcpStream}, 23 | Result, SslOpts, 24 | }; 25 | 26 | impl Stream { 27 | pub fn make_secure(self, host: url::Host, ssl_opts: SslOpts) -> Result { 28 | if self.is_socket() { 29 | // won't secure socket connection 30 | return Ok(self); 31 | } 32 | 33 | let domain = match host { 34 | url::Host::Domain(domain) => domain, 35 | url::Host::Ipv4(ip) => ip.to_string(), 36 | url::Host::Ipv6(ip) => ip.to_string(), 37 | }; 38 | 39 | let mut root_store = RootCertStore::empty(); 40 | root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().map(|x| x.to_owned())); 41 | 42 | if let Some(root_cert_path) = ssl_opts.root_cert_path() { 43 | let mut root_cert_data = vec![]; 44 | let mut root_cert_file = File::open(root_cert_path)?; 45 | root_cert_file.read_to_end(&mut root_cert_data)?; 46 | 47 | let mut root_certs = Vec::new(); 48 | for cert in certs(&mut &*root_cert_data) { 49 | root_certs.push(cert?); 50 | } 51 | 52 | if root_certs.is_empty() && !root_cert_data.is_empty() { 53 | root_certs.push(CertificateDer::from(root_cert_data)); 54 | } 55 | 56 | for cert in &root_certs { 57 | root_store.add(cert.to_owned())?; 58 | } 59 | } 60 | 61 | let config_builder = ClientConfig::builder().with_root_certificates(root_store.clone()); 62 | 63 | let mut config = if let Some(identity) = ssl_opts.client_identity() { 64 | let (cert_chain, priv_key) = identity.load()?; 65 | config_builder.with_client_auth_cert(cert_chain, priv_key)? 66 | } else { 67 | config_builder.with_no_client_auth() 68 | }; 69 | 70 | let server_name = ServerName::try_from(domain.as_str()) 71 | .map_err(|_| webpki::InvalidDnsNameError)? 72 | .to_owned(); 73 | let mut dangerous = config.dangerous(); 74 | let web_pki_verifier = WebPkiServerVerifier::builder(Arc::new(root_store)) 75 | .build() 76 | .map_err(TlsError::from)?; 77 | let dangerous_verifier = DangerousVerifier::new( 78 | ssl_opts.accept_invalid_certs(), 79 | ssl_opts.skip_domain_validation(), 80 | web_pki_verifier, 81 | ); 82 | dangerous.set_certificate_verifier(Arc::new(dangerous_verifier)); 83 | 84 | match self { 85 | Stream::TcpStream(tcp_stream) => match tcp_stream { 86 | TcpStream::Insecure(insecure_stream) => { 87 | let inner = insecure_stream 88 | .into_inner() 89 | .map_err(io::Error::from) 90 | .unwrap(); 91 | let conn = 92 | rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); 93 | let secure_stream = rustls::StreamOwned::new(conn, inner); 94 | Ok(Stream::TcpStream(TcpStream::Secure(BufStream::new( 95 | Box::new(secure_stream), 96 | )))) 97 | } 98 | TcpStream::Secure(_) => Ok(Stream::TcpStream(tcp_stream)), 99 | }, 100 | _ => unreachable!(), 101 | } 102 | } 103 | } 104 | 105 | #[derive(Debug)] 106 | struct DangerousVerifier { 107 | accept_invalid_certs: bool, 108 | skip_domain_validation: bool, 109 | verifier: Arc, 110 | } 111 | 112 | impl DangerousVerifier { 113 | fn new( 114 | accept_invalid_certs: bool, 115 | skip_domain_validation: bool, 116 | verifier: Arc, 117 | ) -> Self { 118 | Self { 119 | accept_invalid_certs, 120 | skip_domain_validation, 121 | verifier, 122 | } 123 | } 124 | } 125 | 126 | impl ServerCertVerifier for DangerousVerifier { 127 | fn verify_server_cert( 128 | &self, 129 | end_entity: &CertificateDer<'_>, 130 | intermediates: &[CertificateDer<'_>], 131 | server_name: &ServerName<'_>, 132 | ocsp_response: &[u8], 133 | now: UnixTime, 134 | ) -> Result { 135 | if self.accept_invalid_certs { 136 | Ok(ServerCertVerified::assertion()) 137 | } else { 138 | match self.verifier.verify_server_cert( 139 | end_entity, 140 | intermediates, 141 | server_name, 142 | ocsp_response, 143 | now, 144 | ) { 145 | Ok(assertion) => Ok(assertion), 146 | Err(Error::InvalidCertificate(CertificateError::NotValidForName)) 147 | if self.skip_domain_validation => 148 | { 149 | Ok(ServerCertVerified::assertion()) 150 | } 151 | Err(e) => Err(e), 152 | } 153 | } 154 | } 155 | 156 | fn verify_tls12_signature( 157 | &self, 158 | message: &[u8], 159 | cert: &CertificateDer<'_>, 160 | dss: &rustls::DigitallySignedStruct, 161 | ) -> Result { 162 | self.verifier.verify_tls12_signature(message, cert, dss) 163 | } 164 | 165 | fn verify_tls13_signature( 166 | &self, 167 | message: &[u8], 168 | cert: &CertificateDer<'_>, 169 | dss: &rustls::DigitallySignedStruct, 170 | ) -> Result { 171 | self.verifier.verify_tls13_signature(message, cert, dss) 172 | } 173 | 174 | fn supported_verify_schemes(&self) -> Vec { 175 | self.verifier.supported_verify_schemes() 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /tests/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAwgrFHsxHXpvGoBCgUkGHlQjYrXju4BrjeT1oup1V/DZ9cbjc 3 | LFcfpMcMHZe9fBZ9RrHmLdCUhTXcULUPlMwR5URGERZjCB8jG2JsEw8goUi9/NoW 4 | n6Wy2GZI1G5RfTsx514s2Ydi/FmjkUUHg0DsLq/iY30QZ7jLJ7Er+DLvVHZ292pE 5 | 4kfN9QO7hclOytqJnznE8BLhxilOpIRLFvbKjZntqe+8OnHfQExo9GSFgRxnYBq3 6 | IxA87RmXNha4DPEbkDURT+tFR3JoX8efFmwIbiH9keXhVMBYqGhjZxqTXD0B4YPy 7 | Og3yPnIK6n6Y20YAo51oTgT6eAvBy6IW4unQAQIDAQABAoIBAQCagmruGCmoWxAx 8 | Oqv+c7GbabznfQlxJ9gEHXL8TfY+68C9wj6HjCRlWB38nmxCl8HPfi7UQmdRH088 9 | cMBkco65acEFMDdRK0IQNzHphz0rUDoAUkR7gTzE9cV+ag23U2BsZGAAIr8eQ95r 10 | +XR+/j5S+rihEd/zR4F1LK3fZ+eM75jX3ai1pkrXs913Wnkl5UzRprYKPMf0p9XN 11 | OoTBvVTYCtzuQHkZHfF0gQ0Cu6eg1yTwqk1e6mgWbeLjSoYSpPYs0ohwECemr40B 12 | ZJU8hITpnZa9VO4p1NEzsIHwFlUUG7F3iLLKE9rTOn15XtvPzp/od6Lii/Qyw8iJ 13 | uIZh+38BAoGBAOGux126Dz/2usu1yNi/pEdm/k3lcN0jKE2Eo6WdIJGgv/SPRY4s 14 | HoBmsnvTzeD3iQxXicJxkUZTjbbmSk+7nNt1Vp07AtxmNLX+fgeHXvwpfh/tODIK 15 | RagCgVCjN4wuyxJx5c6Ddcd1djBrCkBW8zGVhhqghFJwcm6KUvNbQW+xAoGBANwb 16 | 3rvpmwNV+rI2JaIrBxKnq7ZMwHOf1L0blCDnsqTXmgsInXAzVB5Ga5XRbFH0JYFJ 17 | 2ByavCVt/9Gv6tDPwvdhUK38F6CyYurTDQiulyYhQh6W12ANMsN7sO/JAIeXAqN5 18 | 6K8Mfl5yINBeUfxi0N/74PFGRjjJRedRsE8THElRAoGBAIphehuNb7Zf2m/4ejv1 19 | XoztqTcoXckqupa1owZ0zzicYbdadmLTyKwgzIZC/DVacu1fa7gPnO2LZBTnJl+3 20 | gVnLnB1yxrFP9jg14R9KsUJbStvWwCNmywlW5+YC/pd1Rc18i1XSo9B2xM6xg6qp 21 | 5hrcPUtsa1aFXpVLOcvAg+IxAoGAVVxZnPaTr0+A0ew93A5jx9BOv3w0hqLNlilD 22 | 4R76IQOcMfs9U24UiUk2H/v/ziipAuLodO9tV/a451EZ97EbI2USLOc/IosL4ZXH 23 | 0D4lUBxF0Ccfj5iOv1EQ5W1GT085LrzwiDKIL4iDQUvS79ZCuxqrueZPBWbJPVRo 24 | 08HNjoECgYBCgDA+gZRLIlQ99mfEGOyz6J+VWIiSlFEjrlWgEtZ8oDVqAwUgO+oE 25 | J38DjFrnPd4oMb6gS7h1a1tmEI9UFzknbJbo0QK/UT0rlPFgR2jzFzuUD7KOtRW2 26 | +M6MEkMBwt+/FMo/Ni5EJl4jDHnb8U/nKy7bt7BmH59kk9cvNAxziA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEVTCCAz2gAwIBAgIUM3nHiIik1t24hL0OkCEiIFKHYsIwDQYJKoZIhvcNAQEL 3 | BQAwgasxCzAJBgNVBAYTAlJVMQ8wDQYDVQQIDAZNb3djb3cxDzANBgNVBAcMBk1v 4 | c2NvdzEQMA4GA1UECgwHRlNJIFJTTDEiMCAGA1UECwwZcnVzdC1teXNxbC1zaW1w 5 | bGUgdGVzdCBjYTEaMBgGA1UEAwwRcnVzdF9teXNxbF9zaW1wbGUxKDAmBgkqhkiG 6 | 9w0BCQEWGXJvb3RfZW1haWxAcm9vdC5sb2NhbGhvc3QwHhcNMjIxMTExMTI1NjA1 7 | WhcNMzIxMTA4MTI1NjA1WjCBqzELMAkGA1UEBhMCUlUxDzANBgNVBAgMBk1vd2Nv 8 | dzEPMA0GA1UEBwwGTW9zY293MRAwDgYDVQQKDAdGU0kgUlNMMSIwIAYDVQQLDBly 9 | dXN0LW15c3FsLXNpbXBsZSB0ZXN0IGNhMRowGAYDVQQDDBFydXN0X215c3FsX3Np 10 | bXBsZTEoMCYGCSqGSIb3DQEJARYZcm9vdF9lbWFpbEByb290LmxvY2FsaG9zdDCC 11 | ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIKxR7MR16bxqAQoFJBh5UI 12 | 2K147uAa43k9aLqdVfw2fXG43CxXH6THDB2XvXwWfUax5i3QlIU13FC1D5TMEeVE 13 | RhEWYwgfIxtibBMPIKFIvfzaFp+lsthmSNRuUX07MedeLNmHYvxZo5FFB4NA7C6v 14 | 4mN9EGe4yyexK/gy71R2dvdqROJHzfUDu4XJTsraiZ85xPAS4cYpTqSESxb2yo2Z 15 | 7anvvDpx30BMaPRkhYEcZ2AatyMQPO0ZlzYWuAzxG5A1EU/rRUdyaF/HnxZsCG4h 16 | /ZHl4VTAWKhoY2cak1w9AeGD8joN8j5yCup+mNtGAKOdaE4E+ngLwcuiFuLp0AEC 17 | AwEAAaNvMG0wHQYDVR0OBBYEFEBUxV+r++fNBSKzZjHCCfBv6v9LMB8GA1UdIwQY 18 | MBaAFEBUxV+r++fNBSKzZjHCCfBv6v9LMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R 19 | BBMwEYcEfwAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQDB+cew+WQq 20 | sJoFxf2CJNUwYwi/dXGEydI8V55q28gjfpomQwHWqyFve5AjAt93YhRVKAceQJ7h 21 | AksRuTzcFnh1K5Ck+U6TK78aec/FL1Q87GXC/nQoIlKuEDheUHq2fNfDNdssouyK 22 | kJv3p2A14L6U60W3/2TLTzTwS3jeRmK0D08mE0VqOY/eIf3Q8OKQj1udreU9/lEp 23 | C23ifWpJX3WKzdLzZ5voY8N981H6Ih7TXjjjf4AKjDYJXZqyBYjrIrEIiNjTWfxp 24 | k4amKe8T9aObRy1ZAuR91sUy3qZX3Njj5mQTX+6RkVEPdsj4VTL3g1JgdU3Tq53P 25 | m+kOsoKlxwA1 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /tests/client-identity.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackbeam/rust-mysql-simple/000782cc1910c586c815fee2f281f7c95125fa54/tests/client-identity.p12 -------------------------------------------------------------------------------- /tests/client-key.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackbeam/rust-mysql-simple/000782cc1910c586c815fee2f281f7c95125fa54/tests/client-key.der -------------------------------------------------------------------------------- /tests/client-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAoHCE8pwgBvudC43lD3P+QbqlH66Lrjb1MJ8rkS+4JIFSaKIv 3 | V32HIeXGhk3oaQ6CGo+E8nSiToP47s9GvNk86AGFLpvMbQVSliPxlEGrmiVcPyys 4 | mC9FmHaEW68XQKSMxtwf4/NDC1gsIwnT4jfmzF8VLaTQCLD9KPr+o/waRl3cHL3P 5 | 1qMngutYsGmslpY5X9RW9C0AwkP/+dSXmucFW7BauK+f9OlXOKloZKw13BY0ajT3 6 | rz6DJ+LK/qYAvAM0Fjl5wjfVRGqoOTNzzixmbKLqdAdt8vzXmqAQmMy+g6W4C2Zr 7 | +goQ5q5mPrvPScS4uuwiLb7pCUqPga99ryiQiwIDAQABAoIBAGJceXWP+CavzdlO 8 | lfdCYsgDWMayqRoWwX2cqAYr3lYrHs3dWO7nm5hRmcOvMeRuq58DDDvk+7jtOgmW 9 | 9ERFXwzSGce4Zr0T/UzlHm+JT16CtypYBjyLBrzxNDZNgxDzkQc93yNOeXUUCoM0 10 | vD09jncPeBlyqMQbVinwr3rzzVwDqIiojTgxoq9MyR2a/bKQky94bQvc0qOpBn+z 11 | RtUvuW2s4ZGGf0BWIGWaSTop0oc16ulSCyh71WUUVILp2JYgA3Jg8b7jkdkZmype 12 | 1KpIfdzan9YUtXkka7S4kHrEs7W8K+yHRkDCeOksQux7lbkH1btvxiuEiR0CUig0 13 | b68iBnkCgYEA0nZe8I9W8gA/Ruo6xcojowpkChRFb7JPNcxf4RyOXjvsz08v1k24 14 | qFzcouJzINh0tKcRYCm0HinNg1CHdpSGpSBCeVirAt3QPyojlYiqeVY3GYiQ0UW5 15 | hbJDVB7qX39pOG9egyGQpIriNHI7iKkbnYW81y93MuZVO0Q6sIZFiscCgYEAwyda 16 | vOSpX6oj+MheMfdExX7F4pvvMk1v1BsojrT/U7gAkBEruud2nKAH7o6sLBdsmioI 17 | T/+S4dEfDNM+YZSvBFi6Drrn1XlVGyReRopJZ1quoseDDVAbKvU2OcaSASloiLQZ 18 | 4omg/6nL2Y/NlHBKgqQIW/pRpZ/jPuesUsDhaB0CgYBOZ7jAx7WtXDg2lAYnL0IN 19 | eE6CjsC7duMZeLTzaS8EnjB/ntGEddnoJwgvSkt3ngwETQUlHQQ0BIDCfdqpa3Wp 20 | yJXbHRRAciAll+4/w/U2VM8cHQtOWzpdO2bnzMiloRKy6pJ8KaH4GqFgxnm1VMKr 21 | 8WnDhLRUawivlqCCqNL5ewKBgCAvBU/Rhf042eXVZXNoC/dmCMxuWuw4yRB5yh5+ 22 | yvzLg4w+yK9yLKV33tcAwHQlCMwD0ose4uJK0owS6l69Xn+hAk4blNAnyllHjiSj 23 | +acJ1XMS5BH1/AUBm4e7r6hxY8Pnr70kZWDEZ9HhXU31ltQkqRxCE+T0kU12d3zO 24 | Ql4hAoGAevwdcteN8nMOzpHXo7cY2H9TGimKlsowg2riWYWJHndnmSoAgNYKF+l2 25 | n3UAbaowCNRtlQGNFBJLZmqvSbu1ruP2S20ZfWBVm3WFENSpg4gcwT+Y0tB8Xp2r 26 | jQfxFzIY5GLzhIPq7eAg0IXWoZ+AkDbfMN7weKhkdO8FhbuFfww= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/client-key.pkcs8.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackbeam/rust-mysql-simple/000782cc1910c586c815fee2f281f7c95125fa54/tests/client-key.pkcs8.der -------------------------------------------------------------------------------- /tests/client-key.pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCgcITynCAG+50L 3 | jeUPc/5BuqUfrouuNvUwnyuRL7gkgVJooi9XfYch5caGTehpDoIaj4TydKJOg/ju 4 | z0a82TzoAYUum8xtBVKWI/GUQauaJVw/LKyYL0WYdoRbrxdApIzG3B/j80MLWCwj 5 | CdPiN+bMXxUtpNAIsP0o+v6j/BpGXdwcvc/WoyeC61iwaayWljlf1Fb0LQDCQ//5 6 | 1Jea5wVbsFq4r5/06Vc4qWhkrDXcFjRqNPevPoMn4sr+pgC8AzQWOXnCN9VEaqg5 7 | M3POLGZsoup0B23y/NeaoBCYzL6DpbgLZmv6ChDmrmY+u89JxLi67CItvukJSo+B 8 | r32vKJCLAgMBAAECggEAYlx5dY/4Jq/N2U6V90JiyANYxrKpGhbBfZyoBiveVise 9 | zd1Y7uebmFGZw68x5G6rnwMMO+T7uO06CZb0REVfDNIZx7hmvRP9TOUeb4lPXoK3 10 | KlgGPIsGvPE0Nk2DEPORBz3fI055dRQKgzS8PT2Odw94GXKoxBtWKfCvevPNXAOo 11 | iKiNODGir0zJHZr9spCTL3htC9zSo6kGf7NG1S+5bazhkYZ/QFYgZZpJOinShzXq 12 | 6VILKHvVZRRUgunYliADcmDxvuOR2RmbKl7Uqkh93Nqf1hS1eSRrtLiQesSztbwr 13 | 7IdGQMJ46SxC7HuVuQfVu2/GK4SJHQJSKDRvryIGeQKBgQDSdl7wj1byAD9G6jrF 14 | yiOjCmQKFEVvsk81zF/hHI5eO+zPTy/WTbioXNyi4nMg2HS0pxFgKbQeKc2DUId2 15 | lIalIEJ5WKsC3dA/KiOViKp5VjcZiJDRRbmFskNUHupff2k4b16DIZCkiuI0cjuI 16 | qRudhbzXL3cy5lU7RDqwhkWKxwKBgQDDJ1q85KlfqiP4yF4x90TFfsXim+8yTW/U 17 | GyiOtP9TuACQESu653acoAfujqwsF2yaKghP/5Lh0R8M0z5hlK8EWLoOuufVeVUb 18 | JF5GiklnWq6ix4MNUBsq9TY5xpIBKWiItBniiaD/qcvZj82UcEqCpAhb+lGln+M+ 19 | 56xSwOFoHQKBgE5nuMDHta1cODaUBicvQg14ToKOwLt24xl4tPNpLwSeMH+e0YR1 20 | 2egnCC9KS3eeDARNBSUdBDQEgMJ92qlrdanIldsdFEByICWX7j/D9TZUzxwdC05b 21 | Ol07ZufMyKWhErLqknwpofgaoWDGebVUwqvxacOEtFRrCK+WoIKo0vl7AoGAIC8F 22 | T9GF/TjZ5dVlc2gL92YIzG5a7DjJEHnKHn7K/MuDjD7Ir3IspXfe1wDAdCUIzAPS 23 | ix7i4krSjBLqXr1ef6ECThuU0CfKWUeOJKP5pwnVcxLkEfX8BQGbh7uvqHFjw+ev 24 | vSRlYMRn0eFdTfWW1CSpHEIT5PSRTXZ3fM5CXiECgYB6/B1y143ycw7OkdejtxjY 25 | f1MaKYqWyjCDauJZhYked2eZKgCA1goX6XafdQBtqjAI1G2VAY0UEktmaq9Ju7Wu 26 | 4/ZLbRl9YFWbdYUQ1KmDiBzBP5jS0HxenauNB/EXMhjkYvOEg+rt4CDQhdahn4CQ 27 | Nt8w3vB4qGR07wWFu4V/DA== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /tests/client.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 8 (0x8) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: C=RU, ST=Mowcow, L=Moscow, O=FSI RSL, OU=rust-mysql-simple test ca, CN=rust_mysql_simple/emailAddress=root_email@root.localhost 7 | Validity 8 | Not Before: Nov 11 13:19:21 2022 GMT 9 | Not After : Oct 18 13:19:21 2122 GMT 10 | Subject: C=RU, ST=Moscow, O=FSI RSL, OU=rust-mysql-simple test ca, CN=rust_mysql_simple 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | RSA Public-Key: (2048 bit) 14 | Modulus: 15 | 00:a0:70:84:f2:9c:20:06:fb:9d:0b:8d:e5:0f:73: 16 | fe:41:ba:a5:1f:ae:8b:ae:36:f5:30:9f:2b:91:2f: 17 | b8:24:81:52:68:a2:2f:57:7d:87:21:e5:c6:86:4d: 18 | e8:69:0e:82:1a:8f:84:f2:74:a2:4e:83:f8:ee:cf: 19 | 46:bc:d9:3c:e8:01:85:2e:9b:cc:6d:05:52:96:23: 20 | f1:94:41:ab:9a:25:5c:3f:2c:ac:98:2f:45:98:76: 21 | 84:5b:af:17:40:a4:8c:c6:dc:1f:e3:f3:43:0b:58: 22 | 2c:23:09:d3:e2:37:e6:cc:5f:15:2d:a4:d0:08:b0: 23 | fd:28:fa:fe:a3:fc:1a:46:5d:dc:1c:bd:cf:d6:a3: 24 | 27:82:eb:58:b0:69:ac:96:96:39:5f:d4:56:f4:2d: 25 | 00:c2:43:ff:f9:d4:97:9a:e7:05:5b:b0:5a:b8:af: 26 | 9f:f4:e9:57:38:a9:68:64:ac:35:dc:16:34:6a:34: 27 | f7:af:3e:83:27:e2:ca:fe:a6:00:bc:03:34:16:39: 28 | 79:c2:37:d5:44:6a:a8:39:33:73:ce:2c:66:6c:a2: 29 | ea:74:07:6d:f2:fc:d7:9a:a0:10:98:cc:be:83:a5: 30 | b8:0b:66:6b:fa:0a:10:e6:ae:66:3e:bb:cf:49:c4: 31 | b8:ba:ec:22:2d:be:e9:09:4a:8f:81:af:7d:af:28: 32 | 90:8b 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | IP Address:127.0.0.1, DNS:localhost 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 47:6e:41:a6:3e:49:fa:c7:03:a1:01:4c:98:27:19:6f:41:e1: 39 | 5b:a0:99:bc:de:af:91:12:7b:21:7f:9e:3e:6c:05:82:30:c3: 40 | b9:22:52:a0:71:8d:bc:35:b5:8b:39:9b:f0:c6:5c:6f:ee:24: 41 | db:49:22:17:c4:83:62:4a:40:68:fe:ee:e8:99:ec:f3:91:c1: 42 | c2:1b:89:ac:e8:5c:7f:7d:fe:61:9a:fa:67:4b:79:0b:01:d5: 43 | 8f:6b:96:66:47:75:6c:28:7b:ea:3e:69:c7:11:c2:87:df:33: 44 | 0d:6c:35:a6:2e:c0:d4:e3:7d:2a:15:b9:eb:a0:7c:f7:bd:f8: 45 | 76:d4:ce:3f:2a:02:79:2b:8f:7f:33:b1:26:b4:d7:0d:3e:75: 46 | 41:e5:d5:3f:5e:25:a9:66:8f:4d:07:ca:d4:26:d5:17:6c:85: 47 | 7b:cc:e2:bc:03:18:9b:76:b4:78:4a:17:a5:5e:b4:58:fc:61: 48 | df:b5:3d:d1:51:df:fb:65:14:1a:bb:49:70:d3:01:2e:fb:ef: 49 | 73:21:19:a7:d1:0c:82:56:92:70:00:68:6f:e0:76:c2:7a:29: 50 | 3c:21:6b:ac:4f:26:30:15:02:b2:5f:33:80:40:39:e4:97:e3: 51 | b5:76:5b:f1:1a:19:59:fb:87:8e:f1:c6:3e:4d:a9:8e:bc:d3: 52 | 81:a9:1d:3a 53 | -----BEGIN CERTIFICATE----- 54 | MIIDtzCCAp+gAwIBAgIBCDANBgkqhkiG9w0BAQsFADCBqzELMAkGA1UEBhMCUlUx 55 | DzANBgNVBAgMBk1vd2NvdzEPMA0GA1UEBwwGTW9zY293MRAwDgYDVQQKDAdGU0kg 56 | UlNMMSIwIAYDVQQLDBlydXN0LW15c3FsLXNpbXBsZSB0ZXN0IGNhMRowGAYDVQQD 57 | DBFydXN0X215c3FsX3NpbXBsZTEoMCYGCSqGSIb3DQEJARYZcm9vdF9lbWFpbEBy 58 | b290LmxvY2FsaG9zdDAgFw0yMjExMTExMzE5MjFaGA8yMTIyMTAxODEzMTkyMVow 59 | cDELMAkGA1UEBhMCUlUxDzANBgNVBAgMBk1vc2NvdzEQMA4GA1UECgwHRlNJIFJT 60 | TDEiMCAGA1UECwwZcnVzdC1teXNxbC1zaW1wbGUgdGVzdCBjYTEaMBgGA1UEAwwR 61 | cnVzdF9teXNxbF9zaW1wbGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 62 | AQCgcITynCAG+50LjeUPc/5BuqUfrouuNvUwnyuRL7gkgVJooi9XfYch5caGTehp 63 | DoIaj4TydKJOg/juz0a82TzoAYUum8xtBVKWI/GUQauaJVw/LKyYL0WYdoRbrxdA 64 | pIzG3B/j80MLWCwjCdPiN+bMXxUtpNAIsP0o+v6j/BpGXdwcvc/WoyeC61iwaayW 65 | ljlf1Fb0LQDCQ//51Jea5wVbsFq4r5/06Vc4qWhkrDXcFjRqNPevPoMn4sr+pgC8 66 | AzQWOXnCN9VEaqg5M3POLGZsoup0B23y/NeaoBCYzL6DpbgLZmv6ChDmrmY+u89J 67 | xLi67CItvukJSo+Br32vKJCLAgMBAAGjHjAcMBoGA1UdEQQTMBGHBH8AAAGCCWxv 68 | Y2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAR25Bpj5J+scDoQFMmCcZb0HhW6CZ 69 | vN6vkRJ7IX+ePmwFgjDDuSJSoHGNvDW1izmb8MZcb+4k20kiF8SDYkpAaP7u6Jns 70 | 85HBwhuJrOhcf33+YZr6Z0t5CwHVj2uWZkd1bCh76j5pxxHCh98zDWw1pi7A1ON9 71 | KhW566B89734dtTOPyoCeSuPfzOxJrTXDT51QeXVP14lqWaPTQfK1CbVF2yFe8zi 72 | vAMYm3a0eEoXpV60WPxh37U90VHf+2UUGrtJcNMBLvvvcyEZp9EMglaScABob+B2 73 | wnopPCFrrE8mMBUCsl8zgEA55JfjtXZb8RoZWfuHjvHGPk2pjrzTgakdOg== 74 | -----END CERTIFICATE----- 75 | -------------------------------------------------------------------------------- /tests/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA27i5LJ1EvMCy4ybHXpF0H2FP2E7kAhHXEgS+HdXlRcBlFuZ6 3 | PUXamq0tN60WToRAcCoT0sCSosuwEZiDdflHDE91NY+c1IquoOOuvLSN2ZG3FCnY 4 | I4ZqjXeh03g98r3jPiaTeX7qOBpP9ExoGeVVi7xc2I8LlOp6ZHDNpIbKBaOPbm1k 5 | 4HGpxfst9JSDzzvGGsf0K99T9ApMUBBbnUUX9IYuLlItsViVQkZFdI/FZ82tjkJM 6 | fKOkznnIC6XOPpTZN+78GD3aknRuzOQ6C2Rpn9V/uJr72e3zfzmEwK/jP+B31tmy 7 | 41ZipRuH1GB8xohTEKdylRykyd2aCebqZSs+UwIDAQABAoIBAC3ZwmqY9vscizfz 8 | o68MU1spB2xwKgx0hJjx/GpldE6182Jv/hpDNXcJpGH18K2502iUn0ZfgToPn1JM 9 | rUI1Hqpm+mKO8X844XAZkyE13FQGoFnI0Z24MwwKZu6mLOc20PDrQ+MapO3IL98f 10 | AqpEvTFHwWg/kX8l3xyOnZsztXyvXf3vjBK36Y6pb4zpC/WQ/b2rPgBn9Hhm/mxG 11 | GiCFoeNzzRRlQxTWZzST9YpVp7/HOS02yBOtkNrbApKZf85b51IlWtfaXuR7Ks7G 12 | 2IzrNQPmdpsSzAfAThU/SI9rVnOEyvFxQF9WyVhIlVb3GfKuqSGwtTPTkEndFXAi 13 | w4vv04ECgYEA85987CGWxJPv8Zfdm0XufhKyO6rdYARn3LfdewVT8bSx+mNSonXn 14 | +pBf2ajBt0wDevfndXU7VRAanAzPste4/sX1jfFYMAYBgjWvVUyrxIMl8c/2F0mS 15 | 2tC3FS0Mtxh9nG3IO9CxBE4y/3dM8trHJdcWDEwSee0Xbk0hsEUi21ECgYEA5uJg 16 | 0o3Pzvvtd/6CeqUQHvMtFo3sq/lNsti7VK02EJcHCG+uUEQF1KrzyL+wqjKSP2IE 17 | e+8d8zB8HzyIV04Uk9+6vaW2vhzYeAw01M0G7cIYjtp3YptbNjqAYV/mc2HNqJpV 18 | MZO/VmuJtav8r0Agy+FBTaQmKzXGjuzDD8USDmMCgYADk3m/9ZZC+HDzOOl12Aby 19 | LtAS9CUfvbDQmppTrfZXe/GI3WBfJh/rm6bsiP+e8yyx52WsYbYnP39EfyOlWmlK 20 | xBkgpkRHIC9xe9xBAkkbL1mlPQo9uUTAlYO6edjP7zoYy7u+tQeEKqmw+k1U20VF 21 | Fu0p7QvwYjyPz+4IqlsioQKBgQCPePs7vB33fHurhj9koS/sW6aYDmeU0l3jFmUK 22 | kHw5QPCA4I2MmUiaSAUnqV7J1JUmx+0LaqLM3UM0UfRTdFS7M1siwFhXuSsXJlaq 23 | KiH1GxkRpFZtsyayAxaF7AcTiiI3dHgeT7alqDux5gcmjA4VQ4vGCIwja85QQHZS 24 | VV8MCwKBgEpi7hJZrzj3a53lJl2hhVkiMn8xHSng7v5Crz+1Kf0lI2yf22r0sPwK 25 | 7awGTPd40GHvGp5ivp2btpb8/QcmxOjNBooYtQ9Gg7SP6vHZyJaqnU+QDSIYXaRD 26 | 9cCQlHMWPH7K4uc/VFPo2UKnyjNVkEI2FIGyR3rCvKaGkg2lNwCh 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/server.crt: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 9 (0x9) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: C=RU, ST=Mowcow, L=Moscow, O=FSI RSL, OU=rust-mysql-simple test ca, CN=rust_mysql_simple/emailAddress=root_email@root.localhost 7 | Validity 8 | Not Before: Nov 11 13:29:34 2022 GMT 9 | Not After : Oct 18 13:29:34 2122 GMT 10 | Subject: C=RU, ST=Moscow, O=FSI RSL, OU=rust-mysql-simple test server, CN=rust_mysql_simple 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | RSA Public-Key: (2048 bit) 14 | Modulus: 15 | 00:db:b8:b9:2c:9d:44:bc:c0:b2:e3:26:c7:5e:91: 16 | 74:1f:61:4f:d8:4e:e4:02:11:d7:12:04:be:1d:d5: 17 | e5:45:c0:65:16:e6:7a:3d:45:da:9a:ad:2d:37:ad: 18 | 16:4e:84:40:70:2a:13:d2:c0:92:a2:cb:b0:11:98: 19 | 83:75:f9:47:0c:4f:75:35:8f:9c:d4:8a:ae:a0:e3: 20 | ae:bc:b4:8d:d9:91:b7:14:29:d8:23:86:6a:8d:77: 21 | a1:d3:78:3d:f2:bd:e3:3e:26:93:79:7e:ea:38:1a: 22 | 4f:f4:4c:68:19:e5:55:8b:bc:5c:d8:8f:0b:94:ea: 23 | 7a:64:70:cd:a4:86:ca:05:a3:8f:6e:6d:64:e0:71: 24 | a9:c5:fb:2d:f4:94:83:cf:3b:c6:1a:c7:f4:2b:df: 25 | 53:f4:0a:4c:50:10:5b:9d:45:17:f4:86:2e:2e:52: 26 | 2d:b1:58:95:42:46:45:74:8f:c5:67:cd:ad:8e:42: 27 | 4c:7c:a3:a4:ce:79:c8:0b:a5:ce:3e:94:d9:37:ee: 28 | fc:18:3d:da:92:74:6e:cc:e4:3a:0b:64:69:9f:d5: 29 | 7f:b8:9a:fb:d9:ed:f3:7f:39:84:c0:af:e3:3f:e0: 30 | 77:d6:d9:b2:e3:56:62:a5:1b:87:d4:60:7c:c6:88: 31 | 53:10:a7:72:95:1c:a4:c9:dd:9a:09:e6:ea:65:2b: 32 | 3e:53 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Subject Alternative Name: 36 | IP Address:127.0.0.1, DNS:localhost 37 | Signature Algorithm: sha256WithRSAEncryption 38 | 40:c9:47:4c:15:0e:c2:04:6f:de:9e:7b:67:6a:98:26:a6:14: 39 | 04:f1:da:ef:0a:9a:f4:14:bb:f2:ce:5d:f4:d8:cd:ed:2c:cb: 40 | 3e:e0:4b:c5:ca:e3:ad:80:02:eb:73:83:6b:75:e8:cf:86:83: 41 | de:7a:6f:61:03:e7:ff:42:9f:79:fb:33:d0:3c:c6:48:56:17: 42 | 35:c3:22:6c:74:b0:24:85:90:29:07:f2:b7:84:aa:8f:32:1c: 43 | 46:4f:c9:bd:90:53:57:72:37:30:de:2f:3a:35:e5:71:7f:15: 44 | 4a:d8:2a:f3:9a:43:48:e5:72:c1:49:96:0e:31:b2:4a:37:c5: 45 | 28:03:a4:4e:65:78:b1:15:a4:44:9b:dd:5e:16:cb:ac:e8:ad: 46 | 30:d9:8e:f2:8d:96:5c:a9:d9:26:79:b1:f9:c8:f5:2b:b3:92: 47 | 79:37:95:20:42:47:bc:4b:17:86:ce:5e:7d:11:a1:18:1e:d8: 48 | eb:b1:3b:9a:34:29:d3:ba:02:e7:c0:16:c6:dd:a3:91:ba:10: 49 | 0e:9c:b2:7e:60:49:cf:aa:93:08:79:d4:dc:7a:d1:75:19:40: 50 | 0e:29:bd:6e:15:f8:dc:ad:81:e5:4b:b0:ff:2d:04:dc:0b:94: 51 | 65:9f:5d:69:b2:41:21:e4:63:46:fb:ce:5a:b4:47:23:90:42: 52 | 07:4e:23:9e 53 | -----BEGIN CERTIFICATE----- 54 | MIIDuzCCAqOgAwIBAgIBCTANBgkqhkiG9w0BAQsFADCBqzELMAkGA1UEBhMCUlUx 55 | DzANBgNVBAgMBk1vd2NvdzEPMA0GA1UEBwwGTW9zY293MRAwDgYDVQQKDAdGU0kg 56 | UlNMMSIwIAYDVQQLDBlydXN0LW15c3FsLXNpbXBsZSB0ZXN0IGNhMRowGAYDVQQD 57 | DBFydXN0X215c3FsX3NpbXBsZTEoMCYGCSqGSIb3DQEJARYZcm9vdF9lbWFpbEBy 58 | b290LmxvY2FsaG9zdDAgFw0yMjExMTExMzI5MzRaGA8yMTIyMTAxODEzMjkzNFow 59 | dDELMAkGA1UEBhMCUlUxDzANBgNVBAgMBk1vc2NvdzEQMA4GA1UECgwHRlNJIFJT 60 | TDEmMCQGA1UECwwdcnVzdC1teXNxbC1zaW1wbGUgdGVzdCBzZXJ2ZXIxGjAYBgNV 61 | BAMMEXJ1c3RfbXlzcWxfc2ltcGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 62 | CgKCAQEA27i5LJ1EvMCy4ybHXpF0H2FP2E7kAhHXEgS+HdXlRcBlFuZ6PUXamq0t 63 | N60WToRAcCoT0sCSosuwEZiDdflHDE91NY+c1IquoOOuvLSN2ZG3FCnYI4ZqjXeh 64 | 03g98r3jPiaTeX7qOBpP9ExoGeVVi7xc2I8LlOp6ZHDNpIbKBaOPbm1k4HGpxfst 65 | 9JSDzzvGGsf0K99T9ApMUBBbnUUX9IYuLlItsViVQkZFdI/FZ82tjkJMfKOkznnI 66 | C6XOPpTZN+78GD3aknRuzOQ6C2Rpn9V/uJr72e3zfzmEwK/jP+B31tmy41ZipRuH 67 | 1GB8xohTEKdylRykyd2aCebqZSs+UwIDAQABox4wHDAaBgNVHREEEzARhwR/AAAB 68 | gglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAEDJR0wVDsIEb96ee2dqmCam 69 | FATx2u8KmvQUu/LOXfTYze0syz7gS8XK462AAutzg2t16M+Gg956b2ED5/9Cn3n7 70 | M9A8xkhWFzXDImx0sCSFkCkH8reEqo8yHEZPyb2QU1dyNzDeLzo15XF/FUrYKvOa 71 | Q0jlcsFJlg4xsko3xSgDpE5leLEVpESb3V4Wy6zorTDZjvKNllyp2SZ5sfnI9Suz 72 | knk3lSBCR7xLF4bOXn0RoRge2OuxO5o0KdO6AufAFsbdo5G6EA6csn5gSc+qkwh5 73 | 1Nx60XUZQA4pvW4V+NytgeVLsP8tBNwLlGWfXWmyQSHkY0b7zlq0RyOQQgdOI54= 74 | -----END CERTIFICATE----- 75 | --------------------------------------------------------------------------------