ããã¯ããªã«ããããã¦æ¸ãããã®ï¼
RustããMySQLã«ã¢ã¯ã»ã¹ãã¦ã¿ã¾ããããã¨ãããã¨ã§ã
mysqlã¯ã¬ã¼ã
Rustã§MySQLã«ã¢ã¯ã»ã¹ããã«ã¯ãmysqlã¯ã¬ã¼ãã使ãã¿ããã§ãã
ãã¡ãã使ã£ã¦ã¿ã¾ãã
ããããã¼ã¸æ¸ããã¦ãããµã³ãã«ãªã©ãåèã«ã試ãã¦ãã£ã¦ã¿ãã¨ãã¾ãããã
ç°å¢
ä»åã®ç°å¢ã¯ãã¡ãã
$ rustup --version rustup 1.27.1 (54dd3d00f 2024-04-24) info: This is the version for the rustup toolchain manager, not the rustc compiler. info: The currently active `rustc` version is `rustc 1.83.0 (90b35a623 2024-11-26)`
MySQLã¯172.17.0.2ã§ã¢ã¯ã»ã¹ã§ãããã®ã¨ãã¾ãã
MySQL localhost:3306 ssl practice SQL > select version(); +-----------+ | version() | +-----------+ | 8.4.3 | +-----------+ 1 row in set (0.0006 sec)
æºå
Cargoããã±ã¼ã¸ã®ä½æã
$ cargo new --vcs none --lib mysql-getting-started $ cd mysql-getting-started
ãã¹ãã³ã¼ãã®ã¿ã®å®è£ ã«ããã¤ãããªã®ã§ãã©ã¤ãã©ãªã¼ã¯ã¬ã¼ãã«ãã¾ããã
mysqlã¯ã¬ã¼ãã使ã
ã§ã¯ãmysqlã¯ã¬ã¼ãã使ã£ã¦ããã¾ãã
ä¾åé¢ä¿ã®è¿½å ã
$ cargo add mysql
Cargo.toml
[package] name = "mysql-getting-started" version = "0.1.0" edition = "2021" [dependencies] mysql = "25.0.1"
ã¨ããã§ãã½ã¼ã¹ã³ã¼ãããã®ç¶æ ã«ãã¦
src/lib.rs
#[cfg(test)] mod tests { }
ãã«ãããã¨ãã¾ãããã¾ããã
$ cargo build
ã©ããããOpenSSLã®éçºããã±ã¼ã¸ãå¿ è¦ãªããã§ãã
Could not find openssl via pkg-config: pkg-config exited with status code 1 > PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config --libs --cflags openssl The system library `openssl` required by crate `openssl-sys` was not found. The file `openssl.pc` needs to be installed and the PKG_CONFIG_PATH environment variable must contain its parent directory. The PKG_CONFIG_PATH environment variable is not set. HINT: if you have installed the library, try setting PKG_CONFIG_PATH to the directory containing `openssl.pc`. cargo:warning=Could not find directory of OpenSSL installation, and this `-sys` crate cannot proceed without this knowledge. If OpenSSL is installed and this crate had trouble finding it, you can set the `OPENSSL_DIR` environment variable for the compilation process. See stderr section below for further information. --- stderr Could not find directory of OpenSSL installation, and this `-sys` crate cannot proceed without this knowledge. If OpenSSL is installed and this crate had trouble finding it, you can set the `OPENSSL_DIR` environment variable for the compilation process. Make sure you also have the development packages of openssl installed. For example, `libssl-dev` on Ubuntu or `openssl-devel` on Fedora. If you're in a situation where you think the directory *should* be found automatically, please open a bug at https://github.com/sfackler/rust-openssl and include information about your system as well as this message. $HOST = x86_64-unknown-linux-gnu $TARGET = x86_64-unknown-linux-gnu openssl-sys = 0.9.104 warning: build failed, waiting for other jobs to finish...
ã¡ãã»ã¼ã¸ã«å¾ã£ã¦libssl-devãã¤ã³ã¹ãã¼ã«ãã¦ãããã®ã§ãããããã¯SSLï¼TLSã®ããã¯ã¨ã³ããããã©ã«ãã®native-tlsããrustlsã«
åãæ¿ãã¦ã¿ã¾ãããã
$ cargo add mysql --no-default-features --features default-rustls
rustlsã¯Rustã§å®è£ ãããTLSã©ã¤ãã©ãªã¼ã§ãã
Cargo.toml
[package] name = "mysql-getting-started" version = "0.1.0" edition = "2021" [dependencies] mysql = { version = "25.0.1", default-features = false, features = ["default-rustls"] }
ããã§OpenSSLã«ä¾åããªããªãã¾ããã
ãã¹ãã³ã¼ãã®éå½¢ã¯ä»¥ä¸ã®ããã«ãã¾ããã
src/lib.rs
#[cfg(test)] mod tests { use mysql::prelude::Queryable; use mysql::{params, Conn, Opts, OptsBuilder, Pool, PoolConstraints, PoolOpts, TxOpts}; // ããã«ãã¹ããæ¸ãï¼ï¼ }
以éã§ã¯ãã¹ãé¢æ°ãæ¸ãã¤ã¤ãmysqlã¯ã¬ã¼ãã使ã£ã¦ãããã¨æãã¾ãã
MySQLã¸æ¥ç¶ãã
ã¾ãã¯MySQLã¸æ¥ç¶ãã¦ã¿ã¾ãã
ç´æ¥æ¥ç¶ããæ¹æ³ã¨ãã³ãã¯ã·ã§ã³ãã¼ã«ã使ãæ¹æ³ãããã¾ãã
ã¾ãã¯ç´æ¥æ¥ç¶ããæ¹æ³ãããConn
ã使ãã¾ãã
URLæå®ã§æ¥ç¶ãããã¿ã¼ã³ã
#[test] fn connect_mysql() { let url = "mysql://kazuhira:[email protected]:3306/practice"; let opts = Opts::from_url(url).unwrap(); let conn = Conn::new(opts).unwrap(); let version = conn.server_version(); let version_string = format!("{}.{}.{}", version.0, version.1, version.2); assert_eq!(version_string, "8.4.3"); }
Crate mysql / URL-based connection string
QueryStringã§ãã©ã¡ã¼ã¿ã¼ãè¨å®ãããã¨ãã§ãããã®å¤ãã¯Opts
ãè¦ãã°ããã¿ããã§ãã
ã³ãã¯ã·ã§ã³ãã¼ã«ã®è¨å®å 容ãå«ã¾ãã¦ãã¾ãããããã¯æ¥ç¶æ°ã®æå°å¤ãæ大å¤ããããè¨å®å 容ã§ããããã
PoolConstraints in mysql - Rust
OptsBuilder
ã使ã£ã¦ãæ¥ç¶ãã©ã¡ã¼ã¿ã¼ãæ§ç¯ãããã¿ã¼ã³ã
#[test] fn connect_mysql2() { let opts = OptsBuilder::new() .ip_or_hostname(Some("172.17.0.2")) .tcp_port(3306) .user(Some("kazuhira")) .pass(Some("password")) .db_name(Some("practice")); let conn = Conn::new(opts).unwrap(); let version = conn.server_version(); let version_string = format!("{}.{}.{}", version.0, version.1, version.2); assert_eq!(version_string, "8.4.3"); }
ã³ãã¯ã·ã§ã³ãã¼ã«ã使ãå ´åãURLæå®ãOptsBuilder
ãããããå©ç¨ã§ãã¾ããPool
ã使ãã¾ãã
#[test] fn connect_mysql_using_pool() { let url = "mysql://kazuhira:[email protected]:3306/practice"; let pool = Pool::new(url).unwrap(); let conn = pool.get_conn().unwrap(); let version = conn.server_version(); let version_string = format!("{}.{}.{}", version.0, version.1, version.2); assert_eq!(version_string, "8.4.3"); } #[test] fn connect_mysql_using_pool2() { let opts = OptsBuilder::new() .ip_or_hostname(Some("172.17.0.2")) .tcp_port(3306) .user(Some("kazuhira")) .pass(Some("password")) .db_name(Some("practice")) .pool_opts(PoolOpts::new().with_constraints(PoolConstraints::new(10, 10).unwrap())); let pool = Pool::new(opts).unwrap(); let conn = pool.get_conn().unwrap(); let version = conn.server_version(); let version_string = format!("{}.{}.{}", version.0, version.1, version.2); assert_eq!(version_string, "8.4.3"); }
以éã¯ãä»åã¯ã³ãã¯ã·ã§ã³ãã¼ã«ã使ãå¿ è¦ããªãã®ã§
SQLãå®è¡ãã¦ã¿ã
次ã¯SQLãå®è¡ãã¦ã¿ã¾ãã
æ¥ç¶ã表ãConn
ã«SQLãå®è¡ããã¡ã½ãããããã¤ãããã¾ããããã¯Queryable
ã¨ãããã¬ã¤ããå®è£
ãã¦å®ç¾ãã¦ããããã§ãã
Struct mysql::Conn / Trait Implementations / impl Queryable for Conn
ãã¨ãã°query_first
ã
#[test] fn simple_query() { let url = "mysql://kazuhira:[email protected]:3306/practice"; let opts = Opts::from_url(url).unwrap(); let mut conn = Conn::new(opts).unwrap(); let message = conn .query_first::<String, _>("select 'hello'") .unwrap() .unwrap(); assert_eq!(message, "hello"); }
ã©ããè¦ã¦ããã¨ãæ«å°¾ãdrop
ã®ã¡ã½ããã¯çµæãåãåããªããã®ã«ãªãããã§ãã
ãã ãå®éã«ã¯ããªãã¢ã¼ãã¹ãã¼ãã¡ã³ãã使ããã¨ã«ãªãã¨æãã®ã§ãConn
ããç´æ¥SQLãå®è¡ãããã¨ã¯ãããªãã§ããããã
ã¡ãªã¿ã«ãRustã¨MySQLã¨ã®ãã¼ã¿åã®ãããã³ã°ã¯ãã¡ãã«è¨è¼ãããã¾ãã
Crate mysql_common / Supported rust types
追å ã®ä¸æºå
ããããå ã¯ãSQLã®å®è¡ããã©ã³ã¶ã¯ã·ã§ã³ãæ±ãã¾ãã
ãã¼ãã«ããã£ãæ¹ãããã®ã§ãä¾ã®å 容ã使ããã¨ã«ãã¾ãããã
ã¾ããã¹ããå®è¡ããéã«ãConn
ã®ä½æããã³ãã¼ãã«ã®åä½æãè¡ãé¢æ°ãä½æãã¾ããã
fn prepare_test<F>(consumer: F) where F: Fn(&mut Conn), { let url = "mysql://kazuhira:[email protected]:3306/practice"; let opts = Opts::from_url(url).unwrap(); let mut conn = Conn::new(opts).unwrap(); let drop_stmt = conn.prep("drop table if exists payment").unwrap(); conn.exec_drop(&drop_stmt, ()).unwrap(); conn.close(drop_stmt).unwrap(); let create_stmt = conn .prep( "create table payment(\n\ customer_id int,\n\ amount int not null,\n\ account_name text,\n\ primary key(customer_id) )", ) .unwrap(); conn.exec_drop(&create_stmt, ()).unwrap(); conn.close(create_stmt).unwrap(); consumer(&mut conn); }
åãã¹ãå
ã§ã¯ãã®é¢æ°ãå¼ã³åºããConn
ãåãåãé¢æ°ã§ãã¹ããå®è£
ãããã¨ã«ãã¾ãã
åãååã®ãã¼ãã«ãdrop ï¼ createããã®ã§ããã¹ãã®åæå®è¡ã¯ã§ãã¾ãããcargo test
ã¯ããã©ã«ãã§ãã¹ãã並åå®è¡ããããããªã®ã§ã
以ä¸ã®ããã«ãã¦ã¹ã¬ããæ°ã1ã«ãã¦ããå¿
è¦ãããã¾ãã
$ cargo test -- --test-threads=1
insertï¼select
ç¨æããé¢æ°ã使ã£ã¦ãinsertæã¨selectæãå®è¡ãã¦ã¿ã¾ãã
ãããªæãã«ãªãã¾ããã
#[test] fn insert_select() { prepare_test(|conn| { // insert let insert_stmt = conn.prep("insert into payment(customer_id, amount, account_name) values(:customer_id, :amount, :account_name)").unwrap(); conn.exec_drop( &insert_stmt, params! { "customer_id" => 1, "amount" => 2, "account_name" => None::<String> }, ) .unwrap(); conn.exec_drop( &insert_stmt, params! { "customer_id" => 3, "amount" => 4, "account_name" => Some::<String>("foo".into()) }, ) .unwrap(); conn.close(insert_stmt).unwrap(); // select let select_stmt_simply = conn .prep("select customer_id, amount, account_name from payment where customer_id = ?") .unwrap(); let result1 = conn .exec_first::<(i32, i32, Option<String>), _, _>(&select_stmt_simply, (1,)) .unwrap() .map(|(customer_id, amount, account_name)| (customer_id, amount, account_name)) .unwrap(); conn.close(select_stmt_simply).unwrap(); assert_eq!(result1, (1, 2, None::<String>)); let select_stmt_named = conn .prep("select customer_id, amount, account_name from payment where customer_id = :customer_id") .unwrap(); let result2 = conn .exec_first::<(i32, i32, Option<String>), _, _>( &select_stmt_named, params! { "customer_id" => 3}, ) .unwrap() .map(|(customer_id, amount, account_name)| (customer_id, amount, account_name)) .unwrap(); assert_eq!(result2, (3, 4, Some("foo".into()))); let result3 = conn .exec_first::<(i32, i32, Option<String>), _, _>( &select_stmt_named, params! { "customer_id" => 99}, ) .unwrap(); conn.close(select_stmt_named).unwrap(); assert_eq!(result3, None); }); }
ããªãã¢ã¼ãã¹ãã¼ãã¡ã³ãã¯Conn.prep
ã§ä½æãã¾ãããã¬ã¼ã¹ãã«ãã¼ã¯ååä»ããã©ã¡ã¼ã¿ã¼ã¨?
ã使ããã¨ãã§ãã¾ãã
// insert let insert_stmt = conn.prep("insert into payment(customer_id, amount, account_name) values(:customer_id, :amount, :account_name)").unwrap(); // select let select_stmt_simply = conn .prep("select customer_id, amount, account_name from payment where customer_id = ?") .unwrap();
両æ¹ã®æå®æ¹æ³ãæ··å¨ããããã¨ã¯ã§ãã¾ããã
Crate mysql / Named parameters
ååä»ããã©ã¡ã¼ã¿ã¼ã¯params!
ãã¯ãã?
ã®å ´åã¯ã¿ãã«ã§ãã©ã¡ã¼ã¿ã¼ãæå®ãã¾ãã
conn.exec_drop( &insert_stmt, params! { "customer_id" => 1, "amount" => 2, "account_name" => None::<String> }, ) .unwrap(); let result1 = conn .exec_first::<(i32, i32, Option<String>), _, _>(&select_stmt_simply, (1,)) .unwrap() .map(|(customer_id, amount, account_name)| (customer_id, amount, account_name)) .unwrap(); conn.close(select_stmt_simply).unwrap();
ã¯ã¨ãªã¼ãå®è¡ããæã¯ãConn
ã使ã£ã¦ããæã¨ã¯ç°ãªãexec_ã
ã¡ã½ããã使ããã¨ãå¤ããªãã¾ãã
ã¾ãStatement
ãã¯ãã¼ãºãã¦ããã®ã§ãããããã¯ã¹ãã¼ãã¡ã³ããã£ãã·ã¥ãç¡å¹ã®å ´åã«ããã¹ãã ããã§ãã
conn.close(insert_stmt).unwrap();
disabled statement cache means, that you have to close statements yourself using Conn::close, or theyâll exhaust server limits/resources;
ã¹ãã¼ãã¡ã³ããã£ãã·ã¥ãæå¹ãªæ¡ä»¶ã¯ä»¥ä¸ã®ãããªã®ã§ãä»åã¯ãããªãã¦ãããã¯ããªã®ã§ããã
Conn
ãç´æ¥ä½¿ã£ã¦ããæPooledConn
ï¼ã³ãã¯ã·ã§ã³ãã¼ã«ï¼ã使ã£ã¦ããå ´åã¯ä»¥ä¸ã®ããããã®æPoolOpts::reset_connection
ãtruePoolOpts::reset_connection
ãfalseã§ãConn
ã§ã©ããããã¦ããæ
ã¾ãæ§é ä½ã使ã£ã¦ãããæ´æ°ãè¤æ°ä»¶åå¾ã®ãã¿ã¼ã³ãæ¸ãã¦ã¿ã¾ããã
#[derive(Debug, PartialEq, Eq)] struct Payment { customer_id: i32, amount: i32, account_name: Option<String>, } #[test] fn insert_select2() { prepare_test(|conn| { let insert_data = vec![ Payment { customer_id: 1, amount: 2, account_name: None, }, Payment { customer_id: 3, amount: 4, account_name: Some("foo".into()), }, Payment { customer_id: 5, amount: 6, account_name: None, }, Payment { customer_id: 7, amount: 8, account_name: None, }, Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()), }, ]; // insert let insert_stmt = conn.prep("insert into payment(customer_id, amount, account_name) values(:customer_id, :amount, :account_name)").unwrap(); conn.exec_batch( &insert_stmt, insert_data.iter().map(|p| { params! { "customer_id" => p.customer_id, "amount" => p.amount, "account_name" => &p.account_name, } }), ) .unwrap(); conn.close(insert_stmt).unwrap(); // select let select_stmt = conn.prep("select customer_id, amount, account_name from payment where amount > :amount order by customer_id asc") .unwrap(); let results = conn .exec_map( &select_stmt, params! {"amount" => 5}, |(customer_id, amount, account_name)| Payment { customer_id, amount, account_name, }, ) .unwrap(); conn.close(select_stmt).unwrap(); assert_eq!( results, vec![ Payment { customer_id: 5, amount: 6, account_name: None, }, Payment { customer_id: 7, amount: 8, account_name: None, }, Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()), }, ] ); }); }
ãããæ´æ°ãè¡ã£ã¦ãããã«ã¯insertã«ãªããããªãã¨ã¯ãªãã£ãã§ãâ¦ã
ãã©ã³ã¶ã¯ã·ã§ã³
ãã©ã³ã¶ã¯ã·ã§ã³ã«ã¤ãã¦ã¯ãã¡ãã
ãããªæãã§æ¸ãã¦ã¿ã¾ããã
#[test] fn transaction() { prepare_test(|conn| { let mut tx = conn.start_transaction(TxOpts::default()).unwrap(); let insert_data = vec![ Payment { customer_id: 1, amount: 2, account_name: None, }, Payment { customer_id: 3, amount: 4, account_name: Some("foo".into()), }, Payment { customer_id: 5, amount: 6, account_name: None, }, Payment { customer_id: 7, amount: 8, account_name: None, }, Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()), }, ]; // insert let insert_stmt = tx.prep("insert into payment(customer_id, amount, account_name) values(:customer_id, :amount, :account_name)").unwrap(); tx.exec_batch( &insert_stmt, insert_data.iter().map(|p| { params! { "customer_id" => p.customer_id, "amount" => p.amount, "account_name" => &p.account_name, } }), ) .unwrap(); tx.close(insert_stmt).unwrap(); tx.rollback().unwrap(); // ããã§txã¯dropããã let mut tx = conn.start_transaction(TxOpts::default()).unwrap(); let count_stmt = tx.prep("select count(*) from payment").unwrap(); let count_result = tx .exec_first::<i32, _, _>(&count_stmt, ()) .unwrap() .unwrap(); tx.close(count_stmt).unwrap(); assert_eq!(count_result, 0); tx.rollback().unwrap(); let mut tx = conn.start_transaction(TxOpts::default()).unwrap(); // insert let insert_stmt = tx.prep("insert into payment(customer_id, amount, account_name) values(:customer_id, :amount, :account_name)").unwrap(); tx.exec_batch( &insert_stmt, insert_data.iter().map(|p| { params! { "customer_id" => p.customer_id, "amount" => p.amount, "account_name" => &p.account_name, } }), ) .unwrap(); tx.close(insert_stmt).unwrap(); tx.commit().unwrap(); let mut tx = conn.start_transaction(TxOpts::default()).unwrap(); // select let select_stmt = tx.prep("select customer_id, amount, account_name from payment where amount > :amount order by customer_id asc") .unwrap(); let results = tx .exec_map( &select_stmt, params! {"amount" => 5}, |(customer_id, amount, account_name)| Payment { customer_id, amount, account_name, }, ) .unwrap(); tx.close(select_stmt).unwrap(); assert_eq!( results, vec![ Payment { customer_id: 5, amount: 6, account_name: None, }, Payment { customer_id: 7, amount: 8, account_name: None, }, Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()), }, ] ); }); }
Conn.start_transaction
ã§ãã©ã³ã¶ã¯ã·ã§ã³ãéå§ãã¾ãã
let mut tx = conn.start_transaction(TxOpts::default()).unwrap();
ãã©ã³ã¶ã¯ã·ã§ã³å
ã®æä½ã¯ãConn
ã®ä»£ããã«ãã®Transaction
ã使ã£ã¦SQLãå®è¡ãã¾ãã
// insert let insert_stmt = tx.prep("insert into payment(customer_id, amount, account_name) values(:customer_id, :amount, :account_name)").unwrap(); tx.exec_batch( &insert_stmt, insert_data.iter().map(|p| { params! { "customer_id" => p.customer_id, "amount" => p.amount, "account_name" => &p.account_name, } }), ) .unwrap(); tx.close(insert_stmt).unwrap();
ã¡ãã£ã¨å¤ãã£ãã¨ããã¨ãã¦ãã³ãããããã¼ã«ããã¯ãè¡ãã¨ãã®Transaction
ã¯ä½¿ããªããªãã®ã§ãç¶ãã¦ãã©ã³ã¶ã¯ã·ã§ã³å
ã§
æä½ãè¡ãå ´åã¯Transaction
ãéå§ãç´ãå¿
è¦ãããã¾ãã
tx.rollback().unwrap(); // ããã§txã¯dropããã let mut tx = conn.start_transaction(TxOpts::default()).unwrap();
ãã¨ã¯æä½ããã®ãConn
ããTransaction
ã«å¤ãã£ããããã§ãæ±ãæ¹èªä½ã¯å¤§ããå¤ããã¾ããã
ãã¼ã«ããã¯ããå ´åã¯ãã¼ã¿ãç»é²ããã¦ããªããã¨ã
tx.rollback().unwrap(); // ããã§txã¯dropããã let mut tx = conn.start_transaction(TxOpts::default()).unwrap(); let count_stmt = tx.prep("select count(*) from payment").unwrap(); let count_result = tx .exec_first::<i32, _, _>(&count_stmt, ()) .unwrap() .unwrap(); tx.close(count_stmt).unwrap(); assert_eq!(count_result, 0); tx.rollback().unwrap();
ã³ãããããå ´åã¯ãã¼ã¿ãåå¾ã§ãããã¨ã確èªã
tx.commit().unwrap(); let mut tx = conn.start_transaction(TxOpts::default()).unwrap(); // select let select_stmt = tx.prep("select customer_id, amount, account_name from payment where amount > :amount order by customer_id asc") .unwrap(); let results = tx .exec_map( &select_stmt, params! {"amount" => 5}, |(customer_id, amount, account_name)| Payment { customer_id, amount, account_name, }, ) .unwrap(); tx.close(select_stmt).unwrap(); assert_eq!( results, vec![ Payment { customer_id: 5, amount: 6, account_name: None, }, Payment { customer_id: 7, amount: 8, account_name: None, }, Payment { customer_id: 9, amount: 10, account_name: Some("bar".into()), }, ] );
ãããªã¨ããã§ããããã
ãããã«
å®ã¯ãã¨ã¦ãã¨ã¦ãè¦å´ãã¾ããã
ããããããquery_ã
ãexec_ã
ã¡ã½ãããããããããªãã£ãããããã¥ã¡ã³ãã®ãµã³ãã«ããã®ã¾ã¾çä¼¼ãããnull
ã®æ±ãã
ããããããªãã£ããããã¹ãã§å
±éå¦çãä½ããã¨æã£ãããé¢æ°ãå¼æ°ã«åãé¢æ°ã®æ¸ãæ¹ãããããããªãã£ããã
ãªããæ¬æ¥ã®å 容ã¨å ¨ç¶é¢ä¿ãªãã¨ããã§ãããããè¦å´ããæ°ããã¾ãããããRustã®åå¼·ãå ¼ããããããªã¨æãã¾ãã