#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(html_favicon_url = "https://zino.cc/assets/zino-logo.png")]
#![doc(html_logo_url = "https://zino.cc/assets/zino-logo.svg")]
#![allow(async_fn_in_trait)]
#![forbid(unsafe_code)]
use smallvec::SmallVec;
use std::sync::{
atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed},
OnceLock,
};
use zino_core::{extension::TomlTableExt, state::State, LazyLock};
mod accessor;
mod aggregate;
mod column;
mod entity;
mod executor;
mod helper;
mod join;
mod manager;
mod mutation;
mod pool;
mod query;
mod row;
mod schema;
mod transaction;
mod value;
mod window;
pub use accessor::ModelAccessor;
pub use aggregate::Aggregation;
pub use column::EncodeColumn;
pub use entity::Entity;
pub use executor::Executor;
pub use helper::ModelHelper;
pub use join::JoinOn;
pub use manager::PoolManager;
pub use mutation::MutationBuilder;
pub use pool::ConnectionPool;
pub use query::QueryBuilder;
pub use row::DecodeRow;
pub use schema::Schema;
pub use transaction::Transaction;
pub use value::IntoSqlValue;
pub use window::Window;
#[cfg(feature = "orm-sqlx")]
mod decode;
#[cfg(feature = "orm-sqlx")]
mod scalar;
#[cfg(feature = "orm-sqlx")]
pub use decode::{decode, decode_array, decode_decimal, decode_optional, decode_uuid};
#[cfg(feature = "orm-sqlx")]
pub use scalar::ScalarQuery;
cfg_if::cfg_if! {
if #[cfg(any(feature = "orm-mariadb", feature = "orm-mysql", feature = "orm-tidb"))] {
mod mysql;
static DRIVER_NAME: &str = if cfg!(feature = "orm-mariadb") {
"mariadb"
} else if cfg!(feature = "orm-tidb") {
"tidb"
} else {
"mysql"
};
pub type DatabaseDriver = sqlx::MySql;
pub type DatabasePool = sqlx::MySqlPool;
pub type DatabaseConnection = sqlx::MySqlConnection;
pub type DatabaseRow = sqlx::mysql::MySqlRow;
} else if #[cfg(feature = "orm-postgres")] {
mod postgres;
static DRIVER_NAME: &str = "postgres";
pub type DatabaseDriver = sqlx::Postgres;
pub type DatabasePool = sqlx::PgPool;
pub type DatabaseConnection = sqlx::PgConnection;
pub type DatabaseRow = sqlx::postgres::PgRow;
} else {
mod sqlite;
static DRIVER_NAME: &str = "sqlite";
pub type DatabaseDriver = sqlx::Sqlite;
pub type DatabasePool = sqlx::SqlitePool;
pub type DatabaseConnection = sqlx::SqliteConnection;
pub type DatabaseRow = sqlx::sqlite::SqliteRow;
}
}
#[derive(Debug)]
struct ConnectionPools(SmallVec<[ConnectionPool; 4]>);
impl ConnectionPools {
pub(crate) fn get_pool(&self, name: &str) -> Option<&ConnectionPool> {
let mut pool = None;
for cp in self.0.iter().filter(|cp| cp.name() == name) {
if cp.is_available() {
return Some(cp);
} else {
pool = Some(cp);
}
}
pool
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct GlobalPool;
impl GlobalPool {
#[inline]
pub fn get(name: &str) -> Option<&'static ConnectionPool> {
SHARED_CONNECTION_POOLS.get_pool(name)
}
#[inline]
pub async fn connect_all() {
for cp in SHARED_CONNECTION_POOLS.0.iter() {
cp.check_availability().await;
}
}
#[inline]
pub async fn close_all() {
for cp in SHARED_CONNECTION_POOLS.0.iter() {
cp.close().await;
}
}
}
static SHARED_CONNECTION_POOLS: LazyLock<ConnectionPools> = LazyLock::new(|| {
let config = State::shared().config();
let mut database_type = DRIVER_NAME;
if let Some(database) = config.get_table("database") {
if let Some(driver) = database.get_str("type") {
database_type = driver;
}
if let Some(time_zone) = database.get_str("time-zone") {
TIME_ZONE
.set(time_zone)
.expect("fail to set time zone for the database session");
}
if let Some(max_rows) = database.get_usize("max-rows") {
MAX_ROWS.store(max_rows, Relaxed);
}
if let Some(auto_migration) = database.get_bool("auto-migration") {
AUTO_MIGRATION.store(auto_migration, Relaxed);
}
if let Some(debug_only) = database.get_bool("debug-only") {
DEBUG_ONLY.store(debug_only, Relaxed);
}
}
let databases = config.get_array(database_type).unwrap_or_else(|| {
panic!(
"the `{database_type}` field should be an array of tables; \
please use `[[{database_type}]]` to configure a list of database services"
)
});
let pools = databases
.iter()
.filter_map(|v| v.as_table())
.map(ConnectionPool::with_config)
.collect();
let driver = DRIVER_NAME;
if database_type == driver {
tracing::warn!(driver, "connect to database services lazily");
} else {
tracing::error!(
driver,
"invalid database type `{database_type}` for the driver `{driver}`"
);
}
ConnectionPools(pools)
});
static NAMESPACE_PREFIX: LazyLock<&'static str> = LazyLock::new(|| {
State::shared()
.get_config("database")
.and_then(|config| {
config
.get_str("namespace")
.filter(|s| !s.is_empty())
.map(|s| [s, ":"].concat().leak())
})
.unwrap_or_default()
});
static TABLE_PREFIX: LazyLock<&'static str> = LazyLock::new(|| {
State::shared()
.get_config("database")
.and_then(|config| {
config
.get_str("namespace")
.filter(|s| !s.is_empty())
.map(|s| [s, "_"].concat().leak())
})
.unwrap_or_default()
});
static TIME_ZONE: OnceLock<&'static str> = OnceLock::new();
static MAX_ROWS: AtomicUsize = AtomicUsize::new(10000);
static AUTO_MIGRATION: AtomicBool = AtomicBool::new(true);
static DEBUG_ONLY: AtomicBool = AtomicBool::new(false);