1use crate::{db_cmd_strategy::CmdStrategy, error::Result, table_builder::TableBuilder};
2
3#[async_trait]
5pub(crate) trait DBStrategy {
6 fn setup(&self, db_uri: &str) -> Result<()>;
7 fn tear_down(&self, db_uri: &str) -> Result<()>;
8 async fn execute(&self, db_uri: &str, sql: &str) -> Result<()>;
9}
10
11pub struct PostgresDBBuilder {
13 db_uri: String,
14 keep_db: bool,
15 #[cfg(feature = "sqlx")]
16 use_sqlx: bool,
17 schema: String,
18}
19
20impl PostgresDBBuilder {
21 pub fn new(db_uri: impl ToString) -> Self {
22 Self {
23 db_uri: db_uri.to_string(),
24 keep_db: false,
25 #[cfg(feature = "sqlx")]
26 use_sqlx: false,
27 schema: "public".to_string(),
28 }
29 }
30
31 #[must_use]
33 pub fn keep_db(mut self) -> Self {
34 self.keep_db = true;
35 self
36 }
37
38 #[must_use]
40 pub fn schema(mut self, schema: impl ToString) -> Self {
41 self.schema = schema.to_string();
42 self
43 }
44
45 #[must_use]
48 #[cfg(feature = "sqlx")]
49 pub fn use_sqlx(mut self) -> Self {
50 self.use_sqlx = true;
51 self
52 }
53
54 pub async fn start(self) -> Result<PostgresDB> {
55 #[cfg(feature = "sqlx")]
56 let strategy: Box<dyn DBStrategy> = if self.use_sqlx {
57 Box::new(crate::db_sqlx_strategy::SqlxStrategy)
58 } else {
59 Box::new(CmdStrategy)
60 };
61 #[cfg(not(feature = "sqlx"))]
62 let strategy = Box::new(CmdStrategy);
63 let db = PostgresDB {
64 db_uri: self.db_uri,
65 keep_db: self.keep_db,
66 schema: self.schema,
67 strategy,
68 };
69 db.setup().await?;
70 Ok(db)
71 }
72}
73
74pub struct PostgresDB {
76 db_uri: String,
77 keep_db: bool,
78 schema: String,
79 strategy: Box<dyn DBStrategy>,
80}
81
82impl PostgresDB {
83 #[cfg(feature = "sqlx")]
84 pub async fn con(&mut self) -> Result<sqlx::PgConnection> {
85 crate::db_sqlx_strategy::connect(&self.db_uri).await
86 }
87
88 #[cfg(feature = "sqlx")]
89 pub async fn pool(&self) -> Result<sqlx::Pool<sqlx::Postgres>> {
90 crate::db_sqlx_strategy::pool(&self.db_uri).await
91 }
92
93 #[cfg(feature = "sqlx")]
94 pub async fn delete_all_tables(&self) -> Result<()> {
95 use sqlx::Acquire;
99
100 info!("WILL DELETE ALL TABLES");
101
102 let pool = self.pool().await?;
103 let mut t = pool.begin().await?;
104 let con = t.acquire().await?;
105
106 sqlx::query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
107 .execute(&mut *con)
108 .await?;
109
110 let tables: Vec<(String,)> = sqlx::query_as(
111 "
112 SELECT table_name
113 FROM information_schema.tables
114 WHERE table_schema LIKE $1 AND table_type = 'BASE TABLE';",
115 )
116 .bind(&self.schema)
117 .fetch_all(&mut *con)
118 .await?;
119
120 for (table,) in tables {
121 sqlx::query(&(format!("delete from {table};")))
122 .execute(&mut *con)
123 .await?;
124 }
125 t.commit().await?;
126
127 Ok(())
128 }
129
130 pub async fn execute(&self, sql: &str) -> Result<()> {
131 self.strategy.execute(&self.db_uri, sql).await
132 }
133
134 pub async fn create_table<F>(&self, table_name: impl AsRef<str>, tbl_callback: F) -> Result<()>
135 where
136 F: Fn(&mut TableBuilder),
137 {
138 let mut table = TableBuilder::new(format!("{}.{}", self.schema, table_name.as_ref()));
139 tbl_callback(&mut table);
140 let sql = table.to_sql()?;
141 self.execute(sql.as_str()).await
142 }
143
144 pub async fn setup(&self) -> Result<()> {
145 self.strategy.setup(&self.db_uri)
146 }
147
148 fn tear_down(&self) -> Result<()> {
149 if self.keep_db {
150 return Ok(());
151 }
152
153 self.strategy.tear_down(&self.db_uri)
154 }
155}
156
157impl Drop for PostgresDB {
158 fn drop(&mut self) {
159 self.tear_down().expect("teardown");
160 }
161}