pg_embedded_setup_unpriv/cluster/
connection.rs1use camino::{Utf8Path, Utf8PathBuf};
4#[cfg(feature = "diesel-support")]
5use color_eyre::eyre::WrapErr;
6use color_eyre::eyre::eyre;
7use postgres::{Client, NoTls};
8use postgresql_embedded::Settings;
9
10use crate::TestBootstrapSettings;
11use crate::error::BootstrapResult;
12
13pub(crate) fn escape_identifier(name: &str) -> String {
18 name.replace('"', "\"\"")
19}
20
21pub(crate) fn connect_admin(url: &str) -> BootstrapResult<Client> {
26 Client::connect(url, NoTls).map_err(|err| {
27 crate::error::BootstrapError::from(eyre!("failed to connect to admin database: {err}"))
28 })
29}
30
31#[derive(Debug, Clone)]
45pub struct ConnectionMetadata {
46 settings: Settings,
47 pgpass_file: Utf8PathBuf,
48}
49
50impl ConnectionMetadata {
51 pub(crate) fn from_settings(settings: &TestBootstrapSettings) -> Self {
52 Self {
53 settings: settings.settings.clone(),
54 pgpass_file: settings.environment.pgpass_file.clone(),
55 }
56 }
57
58 #[must_use]
60 pub fn host(&self) -> &str {
61 self.settings.host.as_str()
62 }
63
64 #[must_use]
66 pub const fn port(&self) -> u16 {
67 self.settings.port
68 }
69
70 #[must_use]
72 pub fn superuser(&self) -> &str {
73 self.settings.username.as_str()
74 }
75
76 #[must_use]
78 pub fn password(&self) -> &str {
79 self.settings.password.as_str()
80 }
81
82 #[must_use]
84 pub fn pgpass_file(&self) -> &Utf8Path {
85 self.pgpass_file.as_ref()
86 }
87
88 #[must_use]
91 pub fn database_url(&self, database: &str) -> String {
92 self.settings.url(database)
93 }
94}
95
96#[derive(Debug, Clone)]
113pub struct TestClusterConnection {
114 metadata: ConnectionMetadata,
115}
116
117impl TestClusterConnection {
118 pub(crate) fn new(settings: &TestBootstrapSettings) -> Self {
119 Self {
120 metadata: ConnectionMetadata::from_settings(settings),
121 }
122 }
123
124 #[must_use]
126 pub fn host(&self) -> &str {
127 self.metadata.host()
128 }
129
130 #[must_use]
132 pub const fn port(&self) -> u16 {
133 self.metadata.port()
134 }
135
136 #[must_use]
138 pub fn superuser(&self) -> &str {
139 self.metadata.superuser()
140 }
141
142 #[must_use]
144 pub fn password(&self) -> &str {
145 self.metadata.password()
146 }
147
148 #[must_use]
150 pub fn pgpass_file(&self) -> &Utf8Path {
151 self.metadata.pgpass_file()
152 }
153
154 #[must_use]
156 pub fn metadata(&self) -> ConnectionMetadata {
157 self.metadata.clone()
158 }
159
160 #[must_use]
162 pub fn database_url(&self, database: &str) -> String {
163 self.metadata.database_url(database)
164 }
165
166 #[cfg(feature = "diesel-support")]
171 pub fn diesel_connection(&self, database: &str) -> BootstrapResult<diesel::PgConnection> {
172 use diesel::Connection;
173
174 diesel::PgConnection::establish(&self.database_url(database))
175 .wrap_err(format!("failed to connect to {database} via Diesel"))
176 .map_err(crate::error::BootstrapError::from)
177 }
178
179 pub(super) fn admin_client(&self) -> BootstrapResult<Client> {
181 connect_admin(&self.database_url("postgres"))
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use crate::bootstrap::{ExecutionMode, ExecutionPrivileges, TestBootstrapEnvironment};
189 use crate::{CleanupMode, TestBootstrapSettings};
190 use postgresql_embedded::Settings;
191 use std::time::Duration;
192
193 fn sample_settings() -> TestBootstrapSettings {
194 let settings = Settings {
195 host: "127.0.0.1".into(),
196 port: 55_321,
197 username: "fixture_user".into(),
198 password: "fixture_pass".into(),
199 data_dir: "/tmp/cluster-data".into(),
200 installation_dir: "/tmp/cluster-install".into(),
201 ..Settings::default()
202 };
203
204 TestBootstrapSettings {
205 privileges: ExecutionPrivileges::Unprivileged,
206 execution_mode: ExecutionMode::InProcess,
207 settings,
208 environment: TestBootstrapEnvironment {
209 home: Utf8PathBuf::from("/tmp/home"),
210 xdg_cache_home: Utf8PathBuf::from("/tmp/home/cache"),
211 xdg_runtime_dir: Utf8PathBuf::from("/tmp/home/run"),
212 pgpass_file: Utf8PathBuf::from("/tmp/home/.pgpass"),
213 tz_dir: Some(Utf8PathBuf::from("/usr/share/zoneinfo")),
214 timezone: "UTC".into(),
215 },
216 worker_binary: None,
217 setup_timeout: Duration::from_secs(1),
218 start_timeout: Duration::from_secs(1),
219 shutdown_timeout: Duration::from_secs(1),
220 cleanup_mode: CleanupMode::default(),
221 binary_cache_dir: None,
222 }
223 }
224
225 #[test]
226 fn metadata_reflects_underlying_settings() {
227 let settings = sample_settings();
228 let connection = TestClusterConnection::new(&settings);
229 let metadata = connection.metadata();
230
231 assert_eq!(metadata.host(), "127.0.0.1");
232 assert_eq!(metadata.port(), 55_321);
233 assert_eq!(metadata.superuser(), "fixture_user");
234 assert_eq!(metadata.password(), "fixture_pass");
235 assert_eq!(metadata.pgpass_file(), Utf8Path::new("/tmp/home/.pgpass"));
236 }
237
238 #[test]
239 fn database_url_matches_postgresql_embedded() {
240 let settings = sample_settings();
241 let connection = TestClusterConnection::new(&settings);
242 let expected = settings.settings.url("postgres");
243
244 assert_eq!(connection.database_url("postgres"), expected);
245 }
246}