Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement with_init_sql for Postgres, Mysql and Mariadb #182

Merged
merged 7 commits into from
Sep 26, 2024
33 changes: 33 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,36 @@ pub mod zookeeper;

/// Re-exported version of `testcontainers` to avoid version conflicts
pub use testcontainers;

#[cfg(any(feature = "postgres", feature = "mariadb", feature = "mysql"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "postgres", feature = "mariadb", feature = "mysql")))
)]
/// Trait to alight interface for users across different modules.
pub trait InitSql {
/// Registers sql to be executed automatically when the container starts.
///
/// # Example
///
/// ```
/// # #[cfg(feature = "postgres")]
/// # {
/// # use testcontainers_modules::postgres::Postgres;
/// # use testcontainers_modules::InitSql;
/// let postgres_image = Postgres::default().with_init_sql(
/// "CREATE EXTENSION IF NOT EXISTS hstore;"
/// .to_string()
/// .into_bytes(),
/// );
/// # }
/// ```
///
/// ```rust,ignore
/// # use testcontainers_modules::postgres::Postgres;
/// # use testcontainers_modules::rdbms::InitSql;
/// let postgres_image = Postgres::default()
/// .with_init_sql(include_str!("path_to_init.sql").to_string().into_bytes());
/// ```
fn with_init_sql(self, init_sql: impl Into<testcontainers::CopyDataSource>) -> Self;
}
47 changes: 41 additions & 6 deletions src/mariadb/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::borrow::Cow;

use testcontainers::{core::WaitFor, Image};
use testcontainers::{core::WaitFor, CopyDataSource, CopyToContainer, Image};

const NAME: &str = "mariadb";
const TAG: &str = "11.3";
Expand All @@ -27,10 +27,7 @@ const TAG: &str = "11.3";
/// [`MariaDB docker image`]: https://hub.docker.com/_/mariadb
#[derive(Debug, Default, Clone)]
pub struct Mariadb {
/// (remove if there is another variable)
/// Field is included to prevent this struct to be a unit struct.
/// This allows extending functionality (and thus further variables) without breaking changes
_priv: (),
copy_to_sources: Vec<CopyToContainer>,
}

impl Image for Mariadb {
Expand All @@ -57,8 +54,21 @@ impl Image for Mariadb {
("MARIADB_ALLOW_EMPTY_ROOT_PASSWORD", "1"),
]
}
fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
&self.copy_to_sources
}
}
impl crate::InitSql for Mariadb {
fn with_init_sql(mut self, init_sql: impl Into<CopyDataSource>) -> Self {
let target = format!(
"/docker-entrypoint-initdb.d/init_{i}.sql",
i = self.copy_to_sources.len()
);
self.copy_to_sources
.push(CopyToContainer::new(init_sql.into(), target));
self
}
}

#[cfg(test)]
mod tests {
use mysql::prelude::Queryable;
Expand All @@ -69,6 +79,31 @@ mod tests {
testcontainers::{runners::SyncRunner, ImageExt},
};

#[test]
fn mariadb_with_init_sql() -> Result<(), Box<dyn std::error::Error + 'static>> {
use crate::InitSql;
CommanderStorm marked this conversation as resolved.
Show resolved Hide resolved
let node = MariadbImage::default()
.with_init_sql(
"CREATE TABLE foo (bar varchar(255));"
.to_string()
.into_bytes(),
)
.start()?;

let connection_string = &format!(
"mysql://root@{}:{}/test",
node.get_host()?,
node.get_host_port_ipv4(3306.tcp())?
);
let mut conn = mysql::Conn::new(mysql::Opts::from_url(connection_string).unwrap()).unwrap();

let rows: Vec<String> = conn.query("INSERT INTO foo(bar) VALUES ('blub')").unwrap();
assert_eq!(rows.len(), 0);

let rows: Vec<String> = conn.query("SELECT bar FROM foo").unwrap();
assert_eq!(rows.len(), 1);
Ok(())
}
#[test]
fn mariadb_one_plus_one() -> Result<(), Box<dyn std::error::Error + 'static>> {
let mariadb_image = MariadbImage::default();
Expand Down
48 changes: 43 additions & 5 deletions src/mysql/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::borrow::Cow;

use testcontainers::{core::WaitFor, Image};
use testcontainers::{core::WaitFor, CopyDataSource, CopyToContainer, Image};

const NAME: &str = "mysql";
const TAG: &str = "8.1";
Expand All @@ -27,10 +27,7 @@ const TAG: &str = "8.1";
/// [`MySQL docker image`]: https://hub.docker.com/_/mysql
#[derive(Debug, Default, Clone)]
pub struct Mysql {
/// (remove if there is another variable)
/// Field is included to prevent this struct to be a unit struct.
/// This allows extending functionality (and thus further variables) without breaking changes
_priv: (),
copy_to_sources: Vec<CopyToContainer>,
}

impl Image for Mysql {
Expand All @@ -57,17 +54,58 @@ impl Image for Mysql {
("MYSQL_ALLOW_EMPTY_PASSWORD", "yes"),
]
}
fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
&self.copy_to_sources
}
}
impl crate::InitSql for Mysql {
fn with_init_sql(mut self, init_sql: impl Into<CopyDataSource>) -> Self {
let target = format!(
"/docker-entrypoint-initdb.d/init_{i}.sql",
i = self.copy_to_sources.len()
);
self.copy_to_sources
.push(CopyToContainer::new(init_sql.into(), target));
self
}
}

#[cfg(test)]
mod tests {
use mysql::prelude::Queryable;
use testcontainers::core::IntoContainerPort;

use crate::{
mysql::Mysql as MysqlImage,
testcontainers::{runners::SyncRunner, ImageExt},
};

#[test]
fn mysql_with_init_sql() -> Result<(), Box<dyn std::error::Error + 'static>> {
use crate::InitSql;
let node = crate::mysql::Mysql::default()
.with_init_sql(
"CREATE TABLE foo (bar varchar(255));"
.to_string()
.into_bytes(),
)
.start()?;

let connection_string = &format!(
"mysql://root@{}:{}/test",
node.get_host()?,
node.get_host_port_ipv4(3306.tcp())?
);
let mut conn = mysql::Conn::new(mysql::Opts::from_url(connection_string).unwrap()).unwrap();

let rows: Vec<String> = conn.query("INSERT INTO foo(bar) VALUES ('blub')").unwrap();
assert_eq!(rows.len(), 0);

let rows: Vec<String> = conn.query("SELECT bar FROM foo").unwrap();
assert_eq!(rows.len(), 1);
Ok(())
}

#[test]
fn mysql_one_plus_one() -> Result<(), Box<dyn std::error::Error + 'static>> {
let mysql_image = MysqlImage::default();
Expand Down
50 changes: 48 additions & 2 deletions src/postgres/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{borrow::Cow, collections::HashMap};

use testcontainers::{core::WaitFor, Image};
use testcontainers::{core::WaitFor, CopyDataSource, CopyToContainer, Image};

const NAME: &str = "postgres";
const TAG: &str = "11-alpine";
Expand Down Expand Up @@ -30,6 +30,7 @@ const TAG: &str = "11-alpine";
#[derive(Debug, Clone)]
pub struct Postgres {
env_vars: HashMap<String, String>,
copy_to_sources: Vec<CopyToContainer>,
}

impl Postgres {
Expand Down Expand Up @@ -62,6 +63,17 @@ impl Postgres {
self
}
}
impl crate::InitSql for Postgres {
CommanderStorm marked this conversation as resolved.
Show resolved Hide resolved
fn with_init_sql(mut self, init_sql: impl Into<CopyDataSource>) -> Self {
let target = format!(
"/docker-entrypoint-initdb.d/init_{i}.sql",
i = self.copy_to_sources.len()
);
self.copy_to_sources
.push(CopyToContainer::new(init_sql.into(), target));
self
}
}

impl Default for Postgres {
fn default() -> Self {
Expand All @@ -70,7 +82,10 @@ impl Default for Postgres {
env_vars.insert("POSTGRES_USER".to_owned(), "postgres".to_owned());
env_vars.insert("POSTGRES_PASSWORD".to_owned(), "postgres".to_owned());

Self { env_vars }
Self {
env_vars,
copy_to_sources: Vec::new(),
}
}
}

Expand All @@ -95,6 +110,9 @@ impl Image for Postgres {
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
&self.env_vars
}
fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
&self.copy_to_sources
}
}

#[cfg(test)]
Expand Down Expand Up @@ -144,4 +162,32 @@ mod tests {
assert!(first_column.contains("13"));
Ok(())
}

#[test]
fn postgres_with_init_sql() -> Result<(), Box<dyn std::error::Error + 'static>> {
use crate::InitSql;
let node = Postgres::default()
.with_init_sql(
"CREATE TABLE foo (bar varchar(255));"
.to_string()
.into_bytes(),
)
.start()?;

let connection_string = &format!(
"postgres://postgres:postgres@{}:{}/postgres",
node.get_host()?,
node.get_host_port_ipv4(5432)?
);
let mut conn = postgres::Client::connect(connection_string, postgres::NoTls).unwrap();

let rows = conn
.query("INSERT INTO foo(bar) VALUES ($1)", &[&"blub"])
.unwrap();
assert_eq!(rows.len(), 0);

let rows = conn.query("SELECT bar FROM foo", &[]).unwrap();
assert_eq!(rows.len(), 1);
Ok(())
}
}
Loading