testcontainers_modules/mysql/
mod.rs1use std::borrow::Cow;
2
3use testcontainers::{core::WaitFor, CopyDataSource, CopyToContainer, Image};
4
5const NAME: &str = "mysql";
6const TAG: &str = "8.1";
7
8#[derive(Debug, Default, Clone)]
29pub struct Mysql {
30 copy_to_sources: Vec<CopyToContainer>,
31}
32impl Mysql {
33 pub fn with_init_sql(mut self, init_sql: impl Into<CopyDataSource>) -> Self {
53 let target = format!(
54 "/docker-entrypoint-initdb.d/init_{i}.sql",
55 i = self.copy_to_sources.len()
56 );
57 self.copy_to_sources
58 .push(CopyToContainer::new(init_sql.into(), target));
59 self
60 }
61}
62
63impl Image for Mysql {
64 fn name(&self) -> &str {
65 NAME
66 }
67
68 fn tag(&self) -> &str {
69 TAG
70 }
71
72 fn ready_conditions(&self) -> Vec<WaitFor> {
73 vec![
74 WaitFor::message_on_stderr("X Plugin ready for connections. Bind-address"),
75 WaitFor::message_on_stderr("/usr/sbin/mysqld: ready for connections."),
76 ]
77 }
78
79 fn env_vars(
80 &self,
81 ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
82 [
83 ("MYSQL_DATABASE", "test"),
84 ("MYSQL_ALLOW_EMPTY_PASSWORD", "yes"),
85 ]
86 }
87 fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
88 &self.copy_to_sources
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use mysql::prelude::Queryable;
95 use testcontainers::core::IntoContainerPort;
96
97 use crate::{
98 mysql::Mysql as MysqlImage,
99 testcontainers::{runners::SyncRunner, ImageExt},
100 };
101
102 #[test]
103 fn mysql_with_init_sql() -> Result<(), Box<dyn std::error::Error + 'static>> {
104 let node = crate::mysql::Mysql::default()
105 .with_init_sql(
106 "CREATE TABLE foo (bar varchar(255));"
107 .to_string()
108 .into_bytes(),
109 )
110 .start()?;
111
112 let connection_string = &format!(
113 "mysql://root@{}:{}/test",
114 node.get_host()?,
115 node.get_host_port_ipv4(3306.tcp())?
116 );
117 let mut conn = mysql::Conn::new(mysql::Opts::from_url(connection_string).unwrap()).unwrap();
118
119 let rows: Vec<String> = conn.query("INSERT INTO foo(bar) VALUES ('blub')").unwrap();
120 assert_eq!(rows.len(), 0);
121
122 let rows: Vec<String> = conn.query("SELECT bar FROM foo").unwrap();
123 assert_eq!(rows.len(), 1);
124 Ok(())
125 }
126
127 #[test]
128 fn mysql_one_plus_one() -> Result<(), Box<dyn std::error::Error + 'static>> {
129 let mysql_image = MysqlImage::default();
130 let node = mysql_image.start()?;
131
132 let connection_string = &format!(
133 "mysql://root@{}:{}/mysql",
134 node.get_host()?,
135 node.get_host_port_ipv4(3306)?
136 );
137 let mut conn = mysql::Conn::new(mysql::Opts::from_url(connection_string).unwrap()).unwrap();
138
139 let first_row = conn.query_first("SELECT 1 + 1;").unwrap();
140 assert_eq!(first_row, Some(2));
141
142 let first_column: i32 = first_row.unwrap();
143 assert_eq!(first_column, 2);
144 Ok(())
145 }
146
147 #[test]
148 fn mysql_custom_version() -> Result<(), Box<dyn std::error::Error + 'static>> {
149 let image = MysqlImage::default().with_tag("8.0.34");
150 let node = image.start()?;
151
152 let connection_string = &format!(
153 "mysql://root@{}:{}/mysql",
154 node.get_host()?,
155 node.get_host_port_ipv4(3306)?
156 );
157
158 let mut conn = mysql::Conn::new(mysql::Opts::from_url(connection_string).unwrap()).unwrap();
159 let first_row = conn.query_first("SELECT version()").unwrap();
160 assert_eq!(first_row, Some(String::from("8.0.34")));
161 Ok(())
162 }
163}