#[cfg(feature = "postgres")]
use bytes::BytesMut;
use serde::{Deserialize, Serialize};
#[cfg(feature = "postgres")]
use tokio_postgres::types::{IsNull, ToSql, Type};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DatabaseType {
PostgreSQL,
MySQL,
SQLite,
SQLServer,
}
impl DatabaseType {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::PostgreSQL => "postgresql",
Self::MySQL => "mysql",
Self::SQLite => "sqlite",
Self::SQLServer => "sqlserver",
}
}
}
impl std::fmt::Display for DatabaseType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonbValue {
pub data: serde_json::Value,
}
impl JsonbValue {
#[must_use]
pub const fn new(data: serde_json::Value) -> Self {
Self { data }
}
#[must_use]
pub const fn as_value(&self) -> &serde_json::Value {
&self.data
}
#[must_use]
pub fn into_value(self) -> serde_json::Value {
self.data
}
}
#[cfg(feature = "postgres")]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum QueryParam {
Null,
Bool(bool),
Int(i32),
BigInt(i64),
Float(f32),
Double(f64),
Text(String),
Json(serde_json::Value),
}
#[cfg(feature = "postgres")]
impl From<serde_json::Value> for QueryParam {
fn from(value: serde_json::Value) -> Self {
match value {
serde_json::Value::Null => Self::Null,
serde_json::Value::Bool(b) => Self::Bool(b),
serde_json::Value::Number(n) => {
Self::Text(n.to_string())
},
serde_json::Value::String(s) => Self::Text(s),
serde_json::Value::Array(_) | serde_json::Value::Object(_) => Self::Json(value),
}
}
}
#[cfg(feature = "postgres")]
impl ToSql for QueryParam {
tokio_postgres::types::to_sql_checked!();
fn to_sql(
&self,
ty: &Type,
out: &mut BytesMut,
) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
match self {
Self::Null => Ok(IsNull::Yes),
Self::Bool(b) => b.to_sql(ty, out),
Self::Int(i) => i.to_sql(ty, out),
Self::BigInt(i) => i.to_sql(ty, out),
Self::Float(f) => f.to_sql(ty, out),
Self::Double(f) => f.to_sql(ty, out),
Self::Text(s) => s.to_sql(ty, out),
Self::Json(v) => v.to_sql(ty, out),
}
}
fn accepts(_ty: &Type) -> bool {
true
}
}
#[cfg(feature = "postgres")]
pub fn to_sql_param(param: &QueryParam) -> Box<dyn ToSql + Sync + Send> {
match param {
QueryParam::Null => Box::new(None::<String>),
QueryParam::Bool(b) => Box::new(*b),
QueryParam::Int(i) => Box::new(*i),
QueryParam::BigInt(i) => Box::new(*i),
QueryParam::Float(f) => Box::new(*f),
QueryParam::Double(f) => Box::new(*f),
QueryParam::Text(s) => Box::new(s.clone()),
QueryParam::Json(v) => Box::new(v.clone()),
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct PoolMetrics {
pub total_connections: u32,
pub idle_connections: u32,
pub active_connections: u32,
pub waiting_requests: u32,
}
impl PoolMetrics {
#[must_use]
pub fn utilization(&self) -> f64 {
if self.total_connections == 0 {
return 0.0;
}
f64::from(self.active_connections) / f64::from(self.total_connections)
}
#[must_use]
pub const fn is_exhausted(&self) -> bool {
self.idle_connections == 0 && self.waiting_requests > 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_database_type_as_str() {
assert_eq!(DatabaseType::PostgreSQL.as_str(), "postgresql");
assert_eq!(DatabaseType::MySQL.as_str(), "mysql");
assert_eq!(DatabaseType::SQLite.as_str(), "sqlite");
assert_eq!(DatabaseType::SQLServer.as_str(), "sqlserver");
}
#[test]
fn test_database_type_display() {
assert_eq!(DatabaseType::PostgreSQL.to_string(), "postgresql");
}
#[test]
fn test_jsonb_value() {
let value = serde_json::json!({"id": "123", "name": "test"});
let jsonb = JsonbValue::new(value.clone());
assert_eq!(jsonb.as_value(), &value);
assert_eq!(jsonb.into_value(), value);
}
#[test]
fn test_pool_metrics_utilization() {
let metrics = PoolMetrics {
total_connections: 10,
idle_connections: 5,
active_connections: 5,
waiting_requests: 0,
};
assert!((metrics.utilization() - 0.5).abs() < f64::EPSILON);
assert!(!metrics.is_exhausted());
}
#[test]
fn test_pool_metrics_exhausted() {
let metrics = PoolMetrics {
total_connections: 10,
idle_connections: 0,
active_connections: 10,
waiting_requests: 5,
};
assert!((metrics.utilization() - 1.0).abs() < f64::EPSILON);
assert!(metrics.is_exhausted());
}
}