use std::sync::Arc;
use super::backend::{DriverBackend, GivenRowsSpec, QueryResult, TxType};
use super::context::TransactionContext;
use super::transaction::Transaction;
use crate::error::Result;
use crate::match_request::selected_result_executor::SelectedResultExecutor;
use crate::match_request::{MatchExecutionLimits, ValidatedMatchRequest, ValidatedMatchResult};
use crate::registry::DescriptorRegistry;
pub struct Database {
backend: Box<dyn DriverBackend>,
database_name: String,
}
impl Database {
pub fn with_backend(backend: Box<dyn DriverBackend>, database_name: impl Into<String>) -> Self {
Self {
backend,
database_name: database_name.into(),
}
}
#[cfg(feature = "typedb")]
pub async fn connect(
address: &str,
database: &str,
username: &str,
password: &str,
) -> Result<Self> {
Self::connect_with_options(
address,
database,
username,
password,
super::real_driver::ConnectOptions::default(),
)
.await
}
#[cfg(feature = "typedb")]
pub async fn connect_with_options(
address: &str,
database: &str,
username: &str,
password: &str,
options: super::real_driver::ConnectOptions,
) -> Result<Self> {
let backend =
super::real_driver::RealBackend::connect(address, username, password, options).await?;
Ok(Self {
backend: Box::new(backend),
database_name: database.to_string(),
})
}
pub async fn read_transaction(&self) -> Result<Transaction> {
let tx = self
.backend
.open_transaction(&self.database_name, TxType::Read)
.await?;
Ok(Transaction::new(tx, TxType::Read))
}
pub async fn write_transaction(&self) -> Result<Transaction> {
let tx = self
.backend
.open_transaction(&self.database_name, TxType::Write)
.await?;
Ok(Transaction::new(tx, TxType::Write))
}
pub async fn transaction_context(&self, tx_type: TxType) -> Result<TransactionContext> {
let capabilities = self.backend.match_capabilities();
let tx = self
.backend
.open_transaction(&self.database_name, tx_type)
.await?;
Ok(TransactionContext::new(tx, tx_type, capabilities))
}
pub async fn execute_match(
&self,
registry: &DescriptorRegistry,
validated: &ValidatedMatchRequest,
) -> Result<ValidatedMatchResult> {
self.execute_match_with_limits(registry, validated, MatchExecutionLimits::default())
.await
}
pub async fn execute_match_with_limits(
&self,
registry: &DescriptorRegistry,
validated: &ValidatedMatchRequest,
limits: MatchExecutionLimits,
) -> Result<ValidatedMatchResult> {
SelectedResultExecutor::new(registry, self.backend.match_capabilities(), limits)
.execute_owned(self, validated)
.await
}
pub fn database_name(&self) -> &str {
&self.database_name
}
pub fn is_connected(&self) -> bool {
self.backend.is_open()
}
pub fn server_version(&self) -> Option<type_bridge_core_lib::version::Version> {
self.backend.server_version()
}
pub fn check_schema_annotation_support(&self, typeql: &str) -> Result<()> {
use type_bridge_core_lib::version::{Feature, check_feature_supported};
if let Some(server) = self.server_version()
&& crate::schema::annotations::typeql_uses_schema_annotations(typeql)
{
check_feature_supported(Feature::SchemaAnnotations, &server)
.map_err(crate::error::OrmError::UnsupportedVersion)?;
}
Ok(())
}
pub fn supports_given_stage(&self) -> bool {
use type_bridge_core_lib::version::{Feature, check_feature_supported};
self.backend.supports_given_rows()
&& self
.server_version()
.is_some_and(|server| check_feature_supported(Feature::GivenStage, &server).is_ok())
}
pub fn check_given_stage_support(&self) -> Result<()> {
use type_bridge_core_lib::version::{Feature, check_feature_supported};
let Some(server) = self.server_version() else {
return Err(crate::error::OrmError::QueryExecution(
"given-stage support cannot be proven because the server version is unknown".into(),
));
};
check_feature_supported(Feature::GivenStage, &server)
.map_err(crate::error::OrmError::UnsupportedVersion)?;
if !self.backend.supports_given_rows() {
return Err(crate::error::OrmError::QueryExecution(
"given-stage input rows require an active band-9 provider; the connected server supports the syntax but the negotiated provider cannot transport rows"
.into(),
));
}
Ok(())
}
pub async fn database_exists(&self) -> Result<bool> {
self.backend.database_exists(&self.database_name).await
}
pub async fn create_database(&self) -> Result<()> {
if !self.database_exists().await? {
self.backend.create_database(&self.database_name).await?;
}
Ok(())
}
pub async fn delete_database(&self) -> Result<()> {
if self.database_exists().await? {
self.backend.delete_database(&self.database_name).await?;
}
Ok(())
}
pub async fn schema_text(&self) -> Result<String> {
self.backend.schema_text(&self.database_name).await
}
pub fn into_shared(self) -> Arc<Self> {
Arc::new(self)
}
#[tracing::instrument(skip(self, typeql), fields(db = %self.database_name))]
pub async fn execute_raw(&self, typeql: &str, tx_type: TxType) -> Result<QueryResult> {
let mut tx = self
.backend
.open_transaction(&self.database_name, tx_type)
.await?;
let result = tx.query(typeql).await?;
if matches!(tx_type, TxType::Write | TxType::Schema) {
tx.commit().await?;
}
Ok(result)
}
#[tracing::instrument(skip(self, typeql, rows), fields(db = %self.database_name))]
pub async fn execute_with_rows(
&self,
typeql: &str,
tx_type: TxType,
rows: GivenRowsSpec,
) -> Result<QueryResult> {
self.check_given_stage_support()?;
let mut tx = self
.backend
.open_transaction(&self.database_name, tx_type)
.await?;
let result = match tx.query_with_rows(typeql, rows).await {
Ok(result) => result,
Err(error) => {
if matches!(tx_type, TxType::Write | TxType::Schema) {
let _ = tx.rollback().await;
}
let _ = tx.close().await;
return Err(error);
}
};
if matches!(tx_type, TxType::Write | TxType::Schema)
&& let Err(error) = tx.commit().await
{
let _ = tx.close().await;
return Err(error);
}
tx.close().await?;
Ok(result)
}
}