hyperdb_api/
connection_builder.rs1use std::path::{Path, PathBuf};
5use std::time::Duration;
6
7use crate::connection::{Connection, CreateMode};
8use crate::error::{Error, Result};
9use crate::transport::{detect_transport_type, Transport, TransportType};
10use hyperdb_api_core::client::Config;
11
12#[derive(Debug, Clone)]
51pub struct ConnectionBuilder {
52 endpoint: String,
53 database: Option<PathBuf>,
54 create_mode: CreateMode,
55 user: Option<String>,
56 password: Option<String>,
57 login_timeout: Option<Duration>,
58 query_timeout: Option<Duration>,
60 application_name: Option<String>,
62 transfer_mode: Option<hyperdb_api_core::client::grpc::TransferMode>,
64}
65
66impl Default for ConnectionBuilder {
67 fn default() -> Self {
68 Self::new("localhost:7483")
69 }
70}
71
72impl ConnectionBuilder {
73 pub fn new(endpoint: impl Into<String>) -> Self {
79 Self {
80 endpoint: endpoint.into(),
81 database: None,
82 create_mode: CreateMode::default(),
83 user: Some("tableau_internal_user".to_string()),
84 password: None,
85 login_timeout: None,
86 query_timeout: None,
87 application_name: None,
88 transfer_mode: None, }
90 }
91
92 #[must_use]
93 pub fn database(mut self, path: impl AsRef<Path>) -> Self {
95 self.database = Some(path.as_ref().to_path_buf());
96 self
97 }
98
99 #[must_use]
103 pub fn create_mode(mut self, mode: CreateMode) -> Self {
104 self.create_mode = mode;
105 self
106 }
107
108 #[must_use]
109 pub fn user(mut self, user: impl Into<String>) -> Self {
113 self.user = Some(user.into());
114 self
115 }
116
117 #[must_use]
118 pub fn password(mut self, password: impl Into<String>) -> Self {
120 self.password = Some(password.into());
121 self
122 }
123
124 #[must_use]
126 pub fn login_timeout(mut self, timeout: Duration) -> Self {
127 self.login_timeout = Some(timeout);
128 self
129 }
130
131 #[must_use]
136 pub fn query_timeout(mut self, timeout: Duration) -> Self {
137 self.query_timeout = Some(timeout);
138 self
139 }
140
141 #[must_use]
142 pub fn application_name(mut self, name: impl Into<String>) -> Self {
146 self.application_name = Some(name.into());
147 self
148 }
149
150 #[must_use]
151 pub fn auth(mut self, user: impl Into<String>, password: impl Into<String>) -> Self {
153 self.user = Some(user.into());
154 self.password = Some(password.into());
155 self
156 }
157
158 #[must_use]
159 pub fn create_new_database(mut self, database_path: impl AsRef<Path>) -> Self {
161 self.database = Some(database_path.as_ref().to_path_buf());
162 self.create_mode = CreateMode::Create;
163 self
164 }
165
166 #[must_use]
167 pub fn create_or_open_database(mut self, database_path: impl AsRef<Path>) -> Self {
169 self.database = Some(database_path.as_ref().to_path_buf());
170 self.create_mode = CreateMode::CreateIfNotExists;
171 self
172 }
173
174 #[must_use]
175 pub fn open_database(mut self, database_path: impl AsRef<Path>) -> Self {
177 self.database = Some(database_path.as_ref().to_path_buf());
178 self.create_mode = CreateMode::DoNotCreate;
179 self
180 }
181
182 #[must_use]
205 pub fn transfer_mode(mut self, mode: hyperdb_api_core::client::grpc::TransferMode) -> Self {
206 self.transfer_mode = Some(mode);
207 self
208 }
209
210 pub fn build(self) -> Result<Connection> {
220 let transport_type = detect_transport_type(&self.endpoint);
221
222 match transport_type {
223 TransportType::Tcp => self.build_tcp(),
224 #[cfg(unix)]
225 TransportType::UnixSocket => self.build_unix(),
226 #[cfg(windows)]
227 TransportType::NamedPipe => self.build_named_pipe(),
228 TransportType::Grpc => self.build_grpc(),
229 }
230 }
231
232 fn build_tcp(self) -> Result<Connection> {
234 let mut config: Config = self
235 .endpoint
236 .parse()
237 .map_err(|e| Error::new(format!("invalid endpoint: {e}")))?;
238
239 if let Some(user) = &self.user {
240 config = config.with_user(user);
241 }
242
243 if let Some(password) = &self.password {
244 config = config.with_password(password);
245 }
246
247 if let Some(ref app_name) = self.application_name {
248 config = config.with_application_name(app_name);
249 }
250
251 if let Some(timeout) = self.login_timeout {
252 config = config.with_connect_timeout(timeout);
253 }
254
255 let db_path_str = self
256 .database
257 .as_ref()
258 .map(|p| p.to_string_lossy().to_string());
259
260 let client = hyperdb_api_core::client::Client::connect(&config)?;
261
262 let conn = Connection::from_client(client, db_path_str.clone());
263
264 if let Some(db_path) = db_path_str {
266 conn.handle_creation_mode(&db_path, self.create_mode)?;
267 conn.attach_and_set_path(&db_path)?;
268 }
269
270 Ok(conn)
271 }
272
273 #[cfg(unix)]
275 fn build_unix(self) -> Result<Connection> {
276 use hyperdb_api_core::client::ConnectionEndpoint;
277
278 let socket_path = if self.endpoint.starts_with("tab.domain://") {
280 let endpoint = ConnectionEndpoint::parse(&self.endpoint)
282 .map_err(|e| Error::new(format!("invalid Unix socket endpoint: {e}")))?;
283 match endpoint {
284 ConnectionEndpoint::DomainSocket { directory, name } => directory.join(&name),
285 ConnectionEndpoint::Tcp { .. } => {
286 return Err(Error::new("expected Unix domain socket endpoint"))
287 }
288 }
289 } else {
290 std::path::PathBuf::from(&self.endpoint)
292 };
293
294 let mut config = hyperdb_api_core::client::Config::new();
295
296 if let Some(user) = &self.user {
297 config = config.with_user(user);
298 }
299
300 if let Some(password) = &self.password {
301 config = config.with_password(password);
302 }
303
304 let db_path_str = self
305 .database
306 .as_ref()
307 .map(|p| p.to_string_lossy().to_string());
308
309 let client = hyperdb_api_core::client::Client::connect_unix(&socket_path, &config)?;
311
312 let conn = Connection::from_client(client, db_path_str.clone());
313
314 if let Some(db_path) = db_path_str {
316 conn.handle_creation_mode(&db_path, self.create_mode)?;
317 conn.attach_and_set_path(&db_path)?;
318 }
319
320 Ok(conn)
321 }
322
323 #[cfg(windows)]
325 fn build_named_pipe(self) -> Result<Connection> {
326 use hyperdb_api_core::client::ConnectionEndpoint;
327
328 let pipe_path = if self.endpoint.starts_with("tab.pipe://") {
330 let endpoint = ConnectionEndpoint::parse(&self.endpoint)
332 .map_err(|e| Error::new(format!("invalid named pipe endpoint: {e}")))?;
333 match endpoint {
334 ConnectionEndpoint::NamedPipe { host, name } => {
335 format!(r"\\{host}\pipe\{name}")
336 }
337 _ => return Err(Error::new("expected named pipe endpoint")),
338 }
339 } else {
340 self.endpoint.clone()
342 };
343
344 let mut config = hyperdb_api_core::client::Config::new();
345
346 if let Some(user) = &self.user {
347 config = config.with_user(user);
348 }
349
350 if let Some(password) = &self.password {
351 config = config.with_password(password);
352 }
353
354 let db_path_str = self
355 .database
356 .as_ref()
357 .map(|p| p.to_string_lossy().to_string());
358
359 let client = hyperdb_api_core::client::Client::connect_named_pipe(&pipe_path, &config)?;
361
362 let conn = Connection::from_client(client, db_path_str.clone());
363
364 if let Some(db_path) = db_path_str {
366 conn.handle_creation_mode(&db_path, self.create_mode)?;
367 conn.attach_and_set_path(&db_path)?;
368 }
369
370 Ok(conn)
371 }
372
373 fn build_grpc(self) -> Result<Connection> {
375 if self.create_mode != CreateMode::DoNotCreate {
377 return Err(Error::new(
378 "gRPC transport is read-only. Use CreateMode::DoNotCreate for gRPC connections.",
379 ));
380 }
381
382 let db_path_str = self
383 .database
384 .as_ref()
385 .map(|p| p.to_string_lossy().to_string());
386
387 let mut grpc_config = hyperdb_api_core::client::grpc::GrpcConfig::new(&self.endpoint);
389
390 if let Some(ref db_path) = db_path_str {
391 grpc_config = grpc_config.database(db_path);
392 }
393
394 if let Some(mode) = self.transfer_mode {
396 grpc_config = grpc_config.transfer_mode(mode);
397 }
398
399 let transport = Transport::connect_grpc(grpc_config)?;
401
402 Ok(Connection::from_transport(transport, db_path_str))
403 }
404}