use std::sync::Arc;
use crate::error::{Error, Result};
use crate::internal::InternalConnection;
use crate::tide_warn;
use super::DatabaseHandle;
use super::state::{global_connection_slot, global_db_handle, panic_missing_global_db};
#[derive(Clone)]
enum DatabaseInner {
Global,
Handle(DatabaseHandle),
#[allow(dead_code)]
Disconnected,
}
#[derive(Clone)]
pub struct Database {
inner: DatabaseInner,
}
impl Database {
#[allow(dead_code)]
pub(crate) fn disconnected() -> Self {
Self {
inner: DatabaseInner::Disconnected,
}
}
pub(super) fn from_handle(handle: DatabaseHandle) -> Self {
Self {
inner: DatabaseInner::Handle(handle),
}
}
pub(super) fn global_handle() -> Self {
Self {
inner: DatabaseInner::Global,
}
}
pub(super) fn from_internal_connection(inner: InternalConnection) -> Self {
Self::from_handle(DatabaseHandle::Connection(Arc::new(inner)))
}
pub(super) fn current_handle(&self) -> Result<DatabaseHandle> {
match &self.inner {
DatabaseInner::Handle(handle) => Ok(handle.clone()),
DatabaseInner::Global => global_connection_slot()
.load_full()
.map(DatabaseHandle::Connection)
.ok_or_else(|| {
Error::connection(
"Global database connection not initialized. \
Call Database::init() or Database::set_global() before using models."
.to_string(),
)
}),
DatabaseInner::Disconnected => Err(Error::connection(
"Global database connection not initialized. \
Call Database::init() or Database::set_global() before using models."
.to_string(),
)),
}
}
pub(crate) fn current_inner(&self) -> Result<Arc<InternalConnection>> {
match self.current_handle()? {
DatabaseHandle::Connection(inner) => Ok(inner),
DatabaseHandle::Transaction(_) => Err(Error::connection(
"Current database context is a transaction, not a pooled database connection"
.to_string(),
)),
#[cfg(test)]
DatabaseHandle::TestScope => {
unreachable!("test scope marker does not carry a pooled connection")
}
}
}
pub(super) fn is_connected(&self) -> bool {
match &self.inner {
DatabaseInner::Handle(_) => true,
DatabaseInner::Global => global_connection_slot().load_full().is_some(),
DatabaseInner::Disconnected => false,
}
}
pub async fn connect(url: &str) -> Result<Self> {
let inner = InternalConnection::connect(url).await?;
Ok(Self::from_internal_connection(inner))
}
pub async fn init(url: &str) -> Result<&'static Self> {
let db = Self::connect(url).await?;
Self::set_global(db)
}
pub fn set_global(db: Self) -> Result<&'static Self> {
let inner = db.current_inner()?;
global_connection_slot().store(Some(inner));
Ok(global_db_handle())
}
pub fn reset_global() {
global_connection_slot().store(None);
}
pub fn global() -> &'static Self {
let db = global_db_handle();
if db.is_connected() {
db
} else {
panic_missing_global_db(
"Global database connection not initialized. \
Call Database::init() or Database::set_global() before using models.",
)
}
}
pub fn try_global() -> Option<Self> {
super::try_db()
}
pub async fn sync(&self) -> Result<()> {
crate::sync::sync_database(self).await
}
#[doc(hidden)]
pub fn __internal_connection(&self) -> Result<crate::internal::OrmConnection> {
Ok(self.current_inner()?.connection().clone())
}
pub fn backend(&self) -> crate::config::DatabaseType {
if let Some(db_type) = crate::config::TideConfig::get_database_type() {
return db_type;
}
match self.__internal_backend() {
Ok(backend) => backend.as_database_type(),
Err(err) => {
tide_warn!(
"Unable to inspect database backend for disconnected handle: {}. Defaulting to Postgres",
err
);
crate::config::DatabaseType::Postgres
}
}
}
#[doc(hidden)]
pub fn __internal_backend(&self) -> Result<crate::internal::Backend> {
use crate::internal::ConnectionTrait;
Ok(match self.current_handle()? {
DatabaseHandle::Connection(inner) => {
crate::internal::Backend::from(inner.connection().get_database_backend())
}
DatabaseHandle::Transaction(tx) => {
crate::internal::Backend::from(tx.as_ref().get_database_backend())
}
#[cfg(test)]
DatabaseHandle::TestScope => {
unreachable!("test scope marker does not carry a database backend")
}
})
}
}
impl std::fmt::Debug for Database {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Database")
.field("connected", &self.is_connected())
.finish()
}
}