testcontainers_modules/databend/
mod.rs

1use std::{borrow::Cow, collections::BTreeMap};
2
3use testcontainers::{
4    core::{wait::HttpWaitStrategy, ContainerPort, WaitFor},
5    Image,
6};
7
8const DEFAULT_IMAGE_NAME: &str = "datafuselabs/databend";
9const DEFAULT_IMAGE_TAG: &str = "v1.2.615";
10
11/// Port that the [`Databend`] container has internally
12/// Can be rebound externally via [`testcontainers::core::ImageExt::with_mapped_port`]
13///
14/// [`Databend`]: https://databend.rs/
15pub const DATABEND_PORT: ContainerPort = ContainerPort::Tcp(8000);
16
17/// Module to work with [`Databend`] inside of tests.
18///
19/// This module is based on the official [`Databend docker image`].
20///
21/// # Example
22/// ```
23/// use testcontainers_modules::{databend, testcontainers::runners::SyncRunner};
24///
25/// let databend = databend::Databend::default().start().unwrap();
26/// let http_port = databend.get_host_port_ipv4(8000).unwrap();
27///
28/// // do something with the started databend instance.
29/// ```
30///
31/// [`Databend`]: https://databend.rs/
32/// [`Databend docker image`]: https://hub.docker.com/r/datafuselabs/databend
33#[derive(Debug, Clone)]
34pub struct Databend {
35    env_vars: BTreeMap<String, String>,
36}
37
38impl Databend {
39    /// Sets the user for the Databend instance.
40    pub fn with_query_user(mut self, user: &str) -> Self {
41        self.env_vars
42            .insert("QUERY_DEFAULT_USER".to_owned(), user.to_owned());
43        self
44    }
45
46    /// Sets the password for the Databend instance.
47    pub fn with_query_password(mut self, password: &str) -> Self {
48        self.env_vars
49            .insert("QUERY_DEFAULT_PASSWORD".to_owned(), password.to_owned());
50        self
51    }
52}
53
54impl Default for Databend {
55    fn default() -> Self {
56        let mut env_vars = BTreeMap::new();
57        env_vars.insert("QUERY_DEFAULT_USER".to_owned(), "databend".to_owned());
58        env_vars.insert("QUERY_DEFAULT_PASSWORD".to_owned(), "databend".to_owned());
59
60        Self { env_vars }
61    }
62}
63
64impl Image for Databend {
65    fn name(&self) -> &str {
66        DEFAULT_IMAGE_NAME
67    }
68
69    fn tag(&self) -> &str {
70        DEFAULT_IMAGE_TAG
71    }
72
73    fn ready_conditions(&self) -> Vec<WaitFor> {
74        vec![WaitFor::http(
75            HttpWaitStrategy::new("/").with_expected_status_code(200_u16),
76        )]
77    }
78
79    fn env_vars(
80        &self,
81    ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
82        &self.env_vars
83    }
84
85    fn expose_ports(&self) -> &[ContainerPort] {
86        &[DATABEND_PORT]
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use databend_driver::Client;
93
94    use crate::{databend::Databend as DatabendImage, testcontainers::runners::AsyncRunner};
95
96    #[tokio::test]
97    async fn test_databend() {
98        let databend = DatabendImage::default().start().await.unwrap();
99        let http_port = databend.get_host_port_ipv4(8000).await.unwrap();
100        // "databend://user:password@localhost:8000/default?sslmode=disable
101        let dsn = format!(
102            "databend://databend:databend@localhost:{}/default?sslmode=disable",
103            http_port
104        );
105        let client = Client::new(dsn.to_string());
106        let conn = client.get_conn().await.unwrap();
107        let row = conn.query_row("select 'hello'", ()).await.unwrap();
108        assert!(row.is_some());
109        let row = row.unwrap();
110        let (val,): (String,) = row.try_into().unwrap();
111        assert_eq!(val, "hello");
112
113        let conn2 = client.get_conn().await.unwrap();
114        let row = conn2.query_row("select 'world'", ()).await.unwrap();
115        assert!(row.is_some());
116        let row = row.unwrap();
117        let (val,): (String,) = row.try_into().unwrap();
118        assert_eq!(val, "world");
119    }
120}