use std::{
collections::{HashMap, HashSet},
fmt,
sync::Arc,
};
use itertools::Itertools;
use tracing::{debug, error};
use crate::{
Credentials, DatabaseManager, DriverOptions, Transaction, TransactionOptions, TransactionType, UserManager,
common::{
Result,
address::Address,
error::{ConnectionError, Error},
},
connection::{runtime::BackgroundRuntime, server_connection::ServerConnection},
};
pub struct TypeDBDriver {
server_connections: HashMap<Address, ServerConnection>,
database_manager: DatabaseManager,
user_manager: UserManager,
background_runtime: Arc<BackgroundRuntime>,
}
impl TypeDBDriver {
const DRIVER_LANG: &'static str = "rust";
const VERSION: &'static str = match option_env!("CARGO_PKG_VERSION") {
None => "0.0.0",
Some(version) => version,
};
pub const DEFAULT_ADDRESS: &'static str = "localhost:1729";
#[cfg_attr(
feature = "sync",
doc = "TypeDBDriver::new(\"127.0.0.1:1729\", Credentials::new(\"username\", \"password\"), DriverOptions::new(true, None))"
)]
#[cfg_attr(
not(feature = "sync"),
doc = "TypeDBDriver::new(\"127.0.0.1:1729\", Credentials::new(\"username\", \"password\"), DriverOptions::new(true, None)).await"
)]
#[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
pub async fn new(
address: impl AsRef<str>,
credentials: Credentials,
driver_options: DriverOptions,
) -> Result<Self> {
debug!("Creating new TypeDB driver connection to {}", address.as_ref());
Self::new_with_description(address, credentials, driver_options, Self::DRIVER_LANG).await
}
#[cfg_attr(
feature = "sync",
doc = "TypeDBDriver::new_with_description(\"127.0.0.1:1729\", Credentials::new(\"username\", \"password\"), DriverOptions::new(true, None), \"rust\")"
)]
#[cfg_attr(
not(feature = "sync"),
doc = "TypeDBDriver::new_with_description(\"127.0.0.1:1729\", Credentials::new(\"username\", \"password\"), DriverOptions::new(true, None), \"rust\").await"
)]
#[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
pub async fn new_with_description(
address: impl AsRef<str>,
credentials: Credentials,
driver_options: DriverOptions,
driver_lang: impl AsRef<str>,
) -> Result<Self> {
debug!("Initializing TypeDB driver with description: {}", driver_lang.as_ref());
let id = address.as_ref().to_string();
let address: Address = id.parse()?;
let background_runtime = Arc::new(BackgroundRuntime::new()?);
debug!("Establishing server connection to {}", address);
let (server_connection, database_info) = ServerConnection::new(
background_runtime.clone(),
address.clone(),
credentials,
driver_options,
driver_lang.as_ref(),
Self::VERSION,
)
.await?;
debug!("Successfully connected to server at {}", address);
let server_connections: HashMap<Address, ServerConnection> = [(address, server_connection)].into();
let database_manager = DatabaseManager::new(server_connections.clone(), database_info)?;
let user_manager = UserManager::new(server_connections.clone());
debug!("Created database manager and user manager");
debug!("TypeDB driver initialization completed successfully");
Ok(Self { server_connections, database_manager, user_manager, background_runtime })
}
#[expect(unused, reason = "automatic discovery is not yet implemented")]
#[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
async fn fetch_server_list(
background_runtime: Arc<BackgroundRuntime>,
addresses: impl IntoIterator<Item = impl AsRef<str>> + Clone,
credentials: Credentials,
driver_options: DriverOptions,
) -> Result<HashSet<Address>> {
let addresses: Vec<Address> = addresses.into_iter().map(|addr| addr.as_ref().parse()).try_collect()?;
for address in &addresses {
let server_connection = ServerConnection::new(
background_runtime.clone(),
address.clone(),
credentials.clone(),
driver_options.clone(),
Self::DRIVER_LANG,
Self::VERSION,
)
.await;
match server_connection {
Ok((server_connection, _)) => match server_connection.servers_all() {
Ok(servers) => return Ok(servers.into_iter().collect()),
Err(Error::Connection(
ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed,
)) => (),
Err(err) => Err(err)?,
},
Err(Error::Connection(
ConnectionError::ServerConnectionFailedStatusError { .. } | ConnectionError::ConnectionFailed,
)) => (),
Err(err) => Err(err)?,
}
}
Err(ConnectionError::ServerConnectionFailed { addresses }.into())
}
pub fn is_open(&self) -> bool {
self.background_runtime.is_open()
}
pub fn databases(&self) -> &DatabaseManager {
&self.database_manager
}
pub fn users(&self) -> &UserManager {
&self.user_manager
}
#[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
pub async fn transaction(
&self,
database_name: impl AsRef<str>,
transaction_type: TransactionType,
) -> Result<Transaction> {
self.transaction_with_options(database_name, transaction_type, TransactionOptions::new()).await
}
#[cfg_attr(feature = "sync", maybe_async::must_be_sync)]
pub async fn transaction_with_options(
&self,
database_name: impl AsRef<str>,
transaction_type: TransactionType,
options: TransactionOptions,
) -> Result<Transaction> {
let database_name = database_name.as_ref();
debug!("Opening transaction for database: {} with type: {:?}", database_name, transaction_type);
let database = self.database_manager.get_cached_or_fetch(database_name).await?;
let transaction_stream = database
.run_failsafe(|database| async move {
database.connection().open_transaction(database.name(), transaction_type, options).await
})
.await?;
debug!("Successfully opened transaction for database: {}", database_name);
Ok(Transaction::new(transaction_stream))
}
pub fn force_close(&self) -> Result {
if !self.is_open() {
return Ok(());
}
debug!("Closing TypeDB driver connection");
let result = self.server_connections.values().map(ServerConnection::force_close).try_collect();
let close_result = self.background_runtime.force_close().and(result);
match &close_result {
Ok(_) => debug!("Successfully closed TypeDB driver connection"),
Err(e) => error!("Failed to close TypeDB driver connection: {}", e),
}
close_result
}
#[expect(unused, reason = "automatic discovery is not yet implemented")]
pub(crate) fn server_count(&self) -> usize {
self.server_connections.len()
}
#[expect(unused, reason = "automatic discovery is not yet implemented")]
pub(crate) fn servers(&self) -> impl Iterator<Item = &Address> {
self.server_connections.keys()
}
#[expect(unused, reason = "automatic discovery is not yet implemented")]
pub(crate) fn connection(&self, id: &Address) -> Option<&ServerConnection> {
self.server_connections.get(id)
}
#[expect(unused, reason = "automatic discovery is not yet implemented")]
pub(crate) fn connections(&self) -> impl Iterator<Item = (&Address, &ServerConnection)> + '_ {
self.server_connections.iter()
}
}
impl fmt::Debug for TypeDBDriver {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Connection").field("server_connections", &self.server_connections).finish()
}
}