use std::future::Future;
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use crate::error::{Error, Result};
use crate::internal::InternalConnection;
use crate::tide_warn;
static GLOBAL_DB: OnceLock<Database> = OnceLock::new();
pub fn db() -> &'static Database {
GLOBAL_DB.get().expect(
"Global database connection not initialized. \
Call Database::init() or Database::set_global() before using models. \
Use try_db() for a non-panicking alternative."
)
}
pub fn require_db() -> Result<&'static Database> {
GLOBAL_DB.get().ok_or_else(|| {
Error::connection(
"Global database connection not initialized. \
Call Database::init() or Database::set_global() before using models."
.to_string(),
)
})
}
pub fn try_db() -> Option<&'static Database> {
GLOBAL_DB.get()
}
pub fn has_global_db() -> bool {
GLOBAL_DB.get().is_some()
}
#[derive(Clone)]
pub struct Database {
inner: Arc<InternalConnection>,
}
impl Database {
pub async fn connect(url: &str) -> Result<Self> {
let inner = InternalConnection::connect(url).await?;
Ok(Self {
inner: Arc::new(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> {
GLOBAL_DB.set(db).map_err(|_| {
Error::configuration("Global database connection already initialized")
})?;
Ok(GLOBAL_DB.get().unwrap())
}
pub fn global() -> &'static Self {
require_db().expect(
"Global database connection not initialized. \
Call Database::init() or Database::set_global() before using models."
)
}
pub fn try_global() -> Option<&'static Self> {
try_db()
}
pub fn builder() -> DatabaseBuilder {
DatabaseBuilder::new()
}
pub async fn transaction<F, T>(&self, f: F) -> Result<T>
where
F: for<'c> FnOnce(&'c Transaction)
-> std::pin::Pin<Box<dyn Future<Output = Result<T>> + Send + 'c>> + Send,
T: Send,
{
use crate::internal::TransactionTrait;
let txn = self.inner.connection()
.begin()
.await
.map_err(|e| Error::transaction(e.to_string()))?;
let tx = Transaction { inner: txn };
match f(&tx).await {
Ok(result) => {
tx.inner.commit().await
.map_err(|e| Error::transaction(e.to_string()))?;
Ok(result)
}
Err(e) => {
let _ = tx.inner.rollback().await;
Err(e)
}
}
}
pub async fn ping(&self) -> Result<bool> {
use crate::internal::ConnectionTrait;
self.inner.connection()
.execute_unprepared("SELECT 1")
.await
.map(|_| true)
.map_err(|e| Error::connection(e.to_string()))
}
pub async fn sync(&self) -> Result<()> {
crate::sync::sync_database(self).await
}
pub async fn raw<T: crate::model::Model>(sql: &str) -> Result<Vec<T>> {
use crate::internal::{ConnectionTrait, Statement, FromQueryResult};
let db = crate::database::require_db()?;
let backend = db.inner.connection().get_database_backend();
let stmt = Statement::from_string(backend, sql.to_string());
let results = db.inner.connection()
.query_all_raw(stmt)
.await
.map_err(|e| Error::query(e.to_string()))?;
let mut models = Vec::new();
for row in results {
let model = <T::Entity as crate::internal::EntityTrait>::Model::from_query_result(&row, "")
.map_err(|e| Error::query(e.to_string()))?;
models.push(T::from_sea_model(model));
}
Ok(models)
}
pub async fn raw_with_params<T: crate::model::Model>(
sql: &str,
params: Vec<crate::internal::Value>,
) -> Result<Vec<T>> {
use crate::internal::{ConnectionTrait, Statement, FromQueryResult};
let db = crate::database::require_db()?;
let backend = db.inner.connection().get_database_backend();
let stmt = Statement::from_sql_and_values(backend, sql, params);
let results = db.inner.connection()
.query_all_raw(stmt)
.await
.map_err(|e| Error::query(e.to_string()))?;
let mut models = Vec::new();
for row in results {
let model = <T::Entity as crate::internal::EntityTrait>::Model::from_query_result(&row, "")
.map_err(|e| Error::query(e.to_string()))?;
models.push(T::from_sea_model(model));
}
Ok(models)
}
pub async fn execute(sql: &str) -> Result<u64> {
use crate::internal::ConnectionTrait;
let db = crate::database::require_db()?;
let result = db.inner.connection()
.execute_unprepared(sql)
.await
.map_err(|e| Error::query(e.to_string()))?;
Ok(result.rows_affected())
}
pub async fn execute_with_params(sql: &str, params: Vec<crate::internal::Value>) -> Result<u64> {
use crate::internal::{ConnectionTrait, Statement};
let db = crate::database::require_db()?;
let backend = db.inner.connection().get_database_backend();
let stmt = Statement::from_sql_and_values(backend, sql, params);
let result = db.inner.connection()
.execute_raw(stmt)
.await
.map_err(|e| Error::query(e.to_string()))?;
Ok(result.rows_affected())
}
pub async fn raw_json(sql: &str) -> Result<Vec<serde_json::Value>> {
use crate::internal::{ConnectionTrait, Statement};
let db = crate::database::require_db()?;
let backend = db.inner.connection().get_database_backend();
let stmt = Statement::from_string(backend, sql.to_string());
let results = db.inner.connection()
.query_all_raw(stmt)
.await
.map_err(|e| Error::query(e.to_string()))?;
let mut json_results = Vec::new();
for row in results {
let mut obj = serde_json::Map::new();
let columns = row.column_names();
for col_name in columns {
let json_val = if let Ok(val) = row.try_get::<Option<bool>>("", &col_name) {
match val {
Some(v) => serde_json::json!(v),
None => serde_json::Value::Null,
}
} else if let Ok(val) = row.try_get::<Option<i64>>("", &col_name) {
match val {
Some(v) => serde_json::json!(v),
None => serde_json::Value::Null,
}
} else if let Ok(val) = row.try_get::<Option<f64>>("", &col_name) {
match val {
Some(v) => serde_json::json!(v),
None => serde_json::Value::Null,
}
} else if let Ok(val) = row.try_get::<Option<String>>("", &col_name) {
match val {
Some(v) => serde_json::json!(v),
None => serde_json::Value::Null,
}
} else {
serde_json::Value::Null
};
obj.insert(col_name.to_string(), json_val);
}
json_results.push(serde_json::Value::Object(obj));
}
Ok(json_results)
}
#[doc(hidden)]
pub fn __internal_connection(&self) -> &crate::internal::DatabaseConnection {
self.inner.connection()
}
pub fn backend(&self) -> crate::config::DatabaseType {
if let Some(db_type) = crate::config::TideConfig::get_database_type() {
return db_type;
}
use crate::internal::DbBackend;
match self.inner.connection().get_database_backend() {
DbBackend::Postgres => crate::config::DatabaseType::Postgres,
DbBackend::MySql => crate::config::DatabaseType::MySQL,
DbBackend::Sqlite => crate::config::DatabaseType::SQLite,
other => {
tide_warn!("Unknown database backend {:?}, defaulting to Postgres", other);
crate::config::DatabaseType::Postgres
}
}
}
#[doc(hidden)]
pub fn __internal_backend(&self) -> crate::internal::DbBackend {
self.inner.connection().get_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", &true)
.finish()
}
}
pub struct Transaction {
inner: crate::internal::DatabaseTransaction,
}
impl Transaction {
pub fn connection(&self) -> &crate::internal::DatabaseTransaction {
&self.inner
}
#[doc(hidden)]
pub fn __internal_transaction(&self) -> &crate::internal::DatabaseTransaction {
&self.inner
}
}
#[derive(Debug, Clone)]
pub struct DatabaseBuilder {
url: Option<String>,
max_connections: Option<u32>,
min_connections: Option<u32>,
connect_timeout: Option<Duration>,
idle_timeout: Option<Duration>,
max_lifetime: Option<Duration>,
}
impl DatabaseBuilder {
pub fn new() -> Self {
Self {
url: None,
max_connections: None,
min_connections: None,
connect_timeout: None,
idle_timeout: None,
max_lifetime: None,
}
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.url = Some(url.into());
self
}
pub fn max_connections(mut self, n: u32) -> Self {
self.max_connections = Some(n);
self
}
pub fn min_connections(mut self, n: u32) -> Self {
self.min_connections = Some(n);
self
}
pub fn connect_timeout(mut self, duration: Duration) -> Self {
self.connect_timeout = Some(duration);
self
}
pub fn idle_timeout(mut self, duration: Duration) -> Self {
self.idle_timeout = Some(duration);
self
}
pub fn max_lifetime(mut self, duration: Duration) -> Self {
self.max_lifetime = Some(duration);
self
}
pub async fn build(self) -> Result<Database> {
let url = self.url.ok_or_else(|| {
Error::configuration("Database URL is required")
})?;
let mut opts = crate::internal::ConnectOptions::new(url);
if let Some(max) = self.max_connections {
opts.max_connections(max);
}
if let Some(min) = self.min_connections {
opts.min_connections(min);
}
if let Some(timeout) = self.connect_timeout {
opts.connect_timeout(timeout);
}
if let Some(timeout) = self.idle_timeout {
opts.idle_timeout(timeout);
}
if let Some(lifetime) = self.max_lifetime {
opts.max_lifetime(lifetime);
}
let conn = crate::internal::SeaDatabase::connect(opts)
.await
.map_err(|e| Error::connection(e.to_string()))?;
Ok(Database {
inner: Arc::new(InternalConnection { conn }),
})
}
}
impl Default for DatabaseBuilder {
fn default() -> Self {
Self::new()
}
}
pub trait Connection: Send + Sync {
#[doc(hidden)]
fn __get_connection(&self) -> ConnectionRef<'_>;
}
#[doc(hidden)]
pub enum ConnectionRef<'a> {
Database(&'a crate::internal::DatabaseConnection),
Transaction(&'a crate::internal::DatabaseTransaction),
}
impl Connection for Database {
fn __get_connection(&self) -> ConnectionRef<'_> {
ConnectionRef::Database(self.__internal_connection())
}
}
impl Connection for Transaction {
fn __get_connection(&self) -> ConnectionRef<'_> {
ConnectionRef::Transaction(self.__internal_transaction())
}
}