pg_embedded_setup_unpriv/cluster/delegation.rs
1//! Delegation methods that forward `TestCluster` calls to `TestClusterConnection`.
2//!
3//! This module provides ergonomic access to database lifecycle operations directly from
4//! `TestCluster`, eliminating the need for callers to explicitly call `.connection()`
5//! before invoking methods like `create_database` or `drop_database`.
6
7use super::{
8 ClusterHandle,
9 TestCluster,
10 lifecycle::DatabaseName,
11 temporary_database::TemporaryDatabase,
12};
13use crate::{CleanupMode, error::BootstrapResult};
14
15/// Generates delegation methods on `TestCluster` that forward to `TestClusterConnection`.
16///
17/// Each invocation generates a method that calls `self.connection().$method(...)`.
18macro_rules! delegate_to_connection {
19 // Single argument, unit return
20 (
21 $(#[$meta:meta])*
22 fn $name:ident(&self, $arg:ident: $arg_ty:ty) -> BootstrapResult<()>
23 ) => {
24 $(#[$meta])*
25 pub fn $name(&self, $arg: $arg_ty) -> BootstrapResult<()> {
26 self.connection().$name($arg)
27 }
28 };
29
30 // Two arguments, unit return
31 (
32 $(#[$meta:meta])*
33 fn $name:ident(&self, $arg1:ident: $arg1_ty:ty, $arg2:ident: $arg2_ty:ty) -> BootstrapResult<()>
34 ) => {
35 $(#[$meta])*
36 pub fn $name(&self, $arg1: $arg1_ty, $arg2: $arg2_ty) -> BootstrapResult<()> {
37 self.connection().$name($arg1, $arg2)
38 }
39 };
40
41 // Single argument, custom return type
42 (
43 $(#[$meta:meta])*
44 fn $name:ident(&self, $arg:ident: $arg_ty:ty) -> BootstrapResult<$ret:ty>
45 ) => {
46 $(#[$meta])*
47 pub fn $name(&self, $arg: $arg_ty) -> BootstrapResult<$ret> {
48 self.connection().$name($arg)
49 }
50 };
51
52 // Two arguments, custom return type
53 (
54 $(#[$meta:meta])*
55 fn $name:ident(&self, $arg1:ident: $arg1_ty:ty, $arg2:ident: $arg2_ty:ty) -> BootstrapResult<$ret:ty>
56 ) => {
57 $(#[$meta])*
58 pub fn $name(&self, $arg1: $arg1_ty, $arg2: $arg2_ty) -> BootstrapResult<$ret> {
59 self.connection().$name($arg1, $arg2)
60 }
61 };
62}
63
64impl TestCluster {
65 /// Overrides the cleanup mode used when the cluster is dropped.
66 ///
67 /// # Examples
68 /// ```no_run
69 /// use pg_embedded_setup_unpriv::{CleanupMode, TestCluster};
70 ///
71 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
72 /// let cluster = TestCluster::new()?.with_cleanup_mode(CleanupMode::Full);
73 /// # drop(cluster);
74 /// # Ok(())
75 /// # }
76 /// ```
77 #[must_use]
78 pub fn with_cleanup_mode(mut self, cleanup_mode: CleanupMode) -> Self {
79 self.guard.bootstrap.cleanup_mode = cleanup_mode;
80 self.handle = ClusterHandle::new(self.guard.bootstrap.clone());
81 self
82 }
83}
84
85impl TestCluster {
86 delegate_to_connection! {
87 /// Creates a new database with the given name.
88 ///
89 /// Delegates to [`crate::TestClusterConnection::create_database`].
90 ///
91 /// # Errors
92 ///
93 /// Returns an error if the database already exists or if the connection
94 /// fails.
95 ///
96 /// # Examples
97 ///
98 /// ```no_run
99 /// use pg_embedded_setup_unpriv::TestCluster;
100 ///
101 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
102 /// let cluster = TestCluster::new()?;
103 /// cluster.create_database("my_test_db")?;
104 /// # Ok(())
105 /// # }
106 /// ```
107 fn create_database(&self, name: impl Into<DatabaseName>) -> BootstrapResult<()>
108 }
109
110 delegate_to_connection! {
111 /// Creates a new database by cloning an existing template.
112 ///
113 /// Delegates to [`crate::TestClusterConnection::create_database_from_template`].
114 ///
115 /// # Errors
116 ///
117 /// Returns an error if the target database already exists, the template
118 /// does not exist, the template has active connections, or if the
119 /// connection fails.
120 ///
121 /// # Examples
122 ///
123 /// ```no_run
124 /// use pg_embedded_setup_unpriv::TestCluster;
125 ///
126 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
127 /// let cluster = TestCluster::new()?;
128 /// cluster.create_database("my_template")?;
129 /// // ... run migrations on my_template ...
130 /// cluster.create_database_from_template("test_db", "my_template")?;
131 /// # Ok(())
132 /// # }
133 /// ```
134 fn create_database_from_template(&self, name: impl Into<DatabaseName>, template: impl Into<DatabaseName>) -> BootstrapResult<()>
135 }
136
137 delegate_to_connection! {
138 /// Drops an existing database.
139 ///
140 /// Delegates to [`crate::TestClusterConnection::drop_database`].
141 ///
142 /// # Errors
143 ///
144 /// Returns an error if the database does not exist, has active connections,
145 /// or if the connection fails.
146 ///
147 /// # Examples
148 ///
149 /// ```no_run
150 /// use pg_embedded_setup_unpriv::TestCluster;
151 ///
152 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
153 /// let cluster = TestCluster::new()?;
154 /// cluster.create_database("temp_db")?;
155 /// cluster.drop_database("temp_db")?;
156 /// # Ok(())
157 /// # }
158 /// ```
159 fn drop_database(&self, name: impl Into<DatabaseName>) -> BootstrapResult<()>
160 }
161
162 delegate_to_connection! {
163 /// Checks whether a database with the given name exists.
164 ///
165 /// Delegates to [`crate::TestClusterConnection::database_exists`].
166 ///
167 /// # Errors
168 ///
169 /// Returns an error if the connection fails.
170 ///
171 /// # Examples
172 ///
173 /// ```no_run
174 /// use pg_embedded_setup_unpriv::TestCluster;
175 ///
176 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
177 /// let cluster = TestCluster::new()?;
178 /// assert!(cluster.database_exists("postgres")?);
179 /// assert!(!cluster.database_exists("nonexistent")?);
180 /// # Ok(())
181 /// # }
182 /// ```
183 fn database_exists(&self, name: impl Into<DatabaseName>) -> BootstrapResult<bool>
184 }
185
186 /// Ensures a template database exists, creating it if necessary.
187 ///
188 /// Delegates to [`crate::TestClusterConnection::ensure_template_exists`].
189 ///
190 /// # Errors
191 ///
192 /// Returns an error if database creation fails or if `setup_fn` returns
193 /// an error.
194 ///
195 /// # Examples
196 ///
197 /// ```no_run
198 /// use pg_embedded_setup_unpriv::TestCluster;
199 ///
200 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
201 /// let cluster = TestCluster::new()?;
202 ///
203 /// // Ensure template exists, running migrations if needed
204 /// cluster.ensure_template_exists("my_template", |db_name| {
205 /// // Run migrations on the newly created template database
206 /// Ok(())
207 /// })?;
208 ///
209 /// // Clone the template for each test
210 /// cluster.create_database_from_template("test_db_1", "my_template")?;
211 /// # Ok(())
212 /// # }
213 /// ```
214 pub fn ensure_template_exists<F>(
215 &self,
216 name: impl Into<DatabaseName>,
217 setup_fn: F,
218 ) -> BootstrapResult<()>
219 where
220 F: FnOnce(&str) -> BootstrapResult<()>,
221 {
222 self.connection().ensure_template_exists(name, setup_fn)
223 }
224
225 delegate_to_connection! {
226 /// Creates a temporary database that is dropped when the guard is dropped.
227 ///
228 /// Delegates to [`crate::TestClusterConnection::temporary_database`].
229 ///
230 /// # Errors
231 ///
232 /// Returns an error if the database already exists or if the connection
233 /// fails.
234 ///
235 /// # Examples
236 ///
237 /// ```no_run
238 /// use pg_embedded_setup_unpriv::TestCluster;
239 ///
240 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
241 /// let cluster = TestCluster::new()?;
242 /// let temp_db = cluster.temporary_database("my_temp_db")?;
243 ///
244 /// // Database is dropped automatically when temp_db goes out of scope
245 /// # Ok(())
246 /// # }
247 /// ```
248 fn temporary_database(&self, name: impl Into<DatabaseName>) -> BootstrapResult<TemporaryDatabase>
249 }
250
251 delegate_to_connection! {
252 /// Creates a temporary database from a template.
253 ///
254 /// Delegates to [`crate::TestClusterConnection::temporary_database_from_template`].
255 ///
256 /// # Errors
257 ///
258 /// Returns an error if the target database already exists, the template
259 /// does not exist, the template has active connections, or if the
260 /// connection fails.
261 ///
262 /// # Examples
263 ///
264 /// ```no_run
265 /// use pg_embedded_setup_unpriv::TestCluster;
266 ///
267 /// # fn main() -> pg_embedded_setup_unpriv::BootstrapResult<()> {
268 /// let cluster = TestCluster::new()?;
269 /// cluster.ensure_template_exists("migrated_template", |_| Ok(()))?;
270 ///
271 /// let temp_db = cluster.temporary_database_from_template("test_db", "migrated_template")?;
272 ///
273 /// // Database is dropped automatically when temp_db goes out of scope
274 /// # Ok(())
275 /// # }
276 /// ```
277 fn temporary_database_from_template(&self, name: impl Into<DatabaseName>, template: impl Into<DatabaseName>) -> BootstrapResult<TemporaryDatabase>
278 }
279}