use super::connection_string::{ConnectionInfo, TransportType};
use super::query_builder::QueryBuilder;
use crate::connection::{Connection, ConnectionConfig, Transport};
#[allow(unused_imports)] use crate::error::WireError;
use crate::stream::JsonStream;
use crate::Result;
use serde::de::DeserializeOwned;
pub struct FraiseClient {
conn: Connection,
}
impl FraiseClient {
pub async fn connect(connection_string: &str) -> Result<Self> {
let info = ConnectionInfo::parse(connection_string)?;
let transport = match info.transport {
TransportType::Tcp => {
let host = info.host.as_ref().ok_or_else(|| {
crate::WireError::Config("TCP transport requires a host".into())
})?;
let port = info.port.ok_or_else(|| {
crate::WireError::Config("TCP transport requires a port".into())
})?;
Transport::connect_tcp(host, port).await?
}
TransportType::Unix => {
let path = info.unix_socket.as_ref().ok_or_else(|| {
crate::WireError::Config("Unix transport requires a socket path".into())
})?;
Transport::connect_unix(path).await?
}
};
let mut conn = Connection::new(transport);
let config = info.to_config();
conn.startup(&config).await?;
Ok(Self { conn })
}
pub async fn connect_tls(
connection_string: &str,
tls_config: crate::connection::TlsConfig,
) -> Result<Self> {
let info = ConnectionInfo::parse(connection_string)?;
let transport = match info.transport {
TransportType::Tcp => {
let host = info.host.as_ref().ok_or_else(|| {
crate::WireError::Config("TCP transport requires a host".into())
})?;
let port = info.port.ok_or_else(|| {
crate::WireError::Config("TCP transport requires a port".into())
})?;
Transport::connect_tcp_tls(host, port, &tls_config).await?
}
TransportType::Unix => {
return Err(crate::WireError::Config(
"TLS is only supported for TCP connections".into(),
));
}
};
let mut conn = Connection::new(transport);
let config = info.to_config();
conn.startup(&config).await?;
Ok(Self { conn })
}
pub async fn connect_with_config(
connection_string: &str,
config: ConnectionConfig,
) -> Result<Self> {
let info = ConnectionInfo::parse(connection_string)?;
let transport = match info.transport {
TransportType::Tcp => {
let host = info.host.as_ref().ok_or_else(|| {
crate::WireError::Config("TCP transport requires a host".into())
})?;
let port = info.port.ok_or_else(|| {
crate::WireError::Config("TCP transport requires a port".into())
})?;
with_connect_timeout(config.connect_timeout, Transport::connect_tcp(host, port))
.await?
}
TransportType::Unix => {
let path = info.unix_socket.as_ref().ok_or_else(|| {
crate::WireError::Config("Unix transport requires a socket path".into())
})?;
with_connect_timeout(config.connect_timeout, Transport::connect_unix(path)).await?
}
};
if let Some(idle) = config.keepalive_idle {
if let Err(e) = transport.apply_keepalive(idle) {
tracing::warn!("Failed to apply TCP keepalive (idle={idle:?}): {e}");
}
}
let mut conn = Connection::new(transport);
conn.startup(&config).await?;
Ok(Self { conn })
}
pub async fn connect_with_config_and_tls(
connection_string: &str,
config: ConnectionConfig,
tls_config: crate::connection::TlsConfig,
) -> Result<Self> {
let info = ConnectionInfo::parse(connection_string)?;
let transport = match info.transport {
TransportType::Tcp => {
let host = info.host.as_ref().ok_or_else(|| {
crate::WireError::Config("TCP transport requires a host".into())
})?;
let port = info.port.ok_or_else(|| {
crate::WireError::Config("TCP transport requires a port".into())
})?;
with_connect_timeout(
config.connect_timeout,
Transport::connect_tcp_tls(host, port, &tls_config),
)
.await?
}
TransportType::Unix => {
return Err(crate::WireError::Config(
"TLS is only supported for TCP connections".into(),
));
}
};
if let Some(idle) = config.keepalive_idle {
if let Err(e) = transport.apply_keepalive(idle) {
tracing::warn!("Failed to apply TCP keepalive (idle={idle:?}): {e}");
}
}
let mut conn = Connection::new(transport);
conn.startup(&config).await?;
Ok(Self { conn })
}
pub fn query<T: DeserializeOwned + std::marker::Unpin + 'static>(
self,
entity: impl Into<String>,
) -> QueryBuilder<T> {
QueryBuilder::new(self, entity)
}
#[allow(clippy::too_many_arguments)] pub(crate) async fn execute_query(
self,
sql: &str,
chunk_size: usize,
max_memory: Option<usize>,
soft_limit_warn_threshold: Option<f32>,
soft_limit_fail_threshold: Option<f32>,
enable_adaptive_chunking: bool,
adaptive_min_chunk_size: Option<usize>,
adaptive_max_chunk_size: Option<usize>,
) -> Result<JsonStream> {
self.conn
.streaming_query(
sql,
chunk_size,
max_memory,
soft_limit_warn_threshold,
soft_limit_fail_threshold,
enable_adaptive_chunking,
adaptive_min_chunk_size,
adaptive_max_chunk_size,
)
.await
}
}
async fn with_connect_timeout<F, T>(timeout: Option<std::time::Duration>, fut: F) -> Result<T>
where
F: std::future::Future<Output = Result<T>>,
{
match timeout {
Some(d) => match tokio::time::timeout(d, fut).await {
Ok(result) => result,
Err(_) => Err(crate::WireError::Connection(format!(
"connection timed out after {d:?}"
))),
},
None => fut.await,
}
}
#[cfg(test)]
mod tests;