use crate::config::DatabaseBackend;
use crate::error::{SqlxError, SqlxResult};
use serde_json::Value as JsonValue;
use sqlx::Row;
pub enum SqlxRow {
#[cfg(feature = "postgres")]
Postgres(sqlx::postgres::PgRow),
#[cfg(feature = "mysql")]
MySql(sqlx::mysql::MySqlRow),
#[cfg(feature = "sqlite")]
Sqlite(sqlx::sqlite::SqliteRow),
}
impl SqlxRow {
pub fn backend(&self) -> DatabaseBackend {
match self {
#[cfg(feature = "postgres")]
Self::Postgres(_) => DatabaseBackend::Postgres,
#[cfg(feature = "mysql")]
Self::MySql(_) => DatabaseBackend::MySql,
#[cfg(feature = "sqlite")]
Self::Sqlite(_) => DatabaseBackend::Sqlite,
}
}
pub fn get<T>(&self, index: usize) -> SqlxResult<T>
where
T: SqlxDecode,
{
T::decode_from_row(self, index)
}
pub fn get_by_name<T>(&self, name: &str) -> SqlxResult<T>
where
T: SqlxDecodeNamed,
{
T::decode_by_name(self, name)
}
pub fn try_get<T>(&self, index: usize) -> SqlxResult<Option<T>>
where
T: SqlxDecode,
{
match T::decode_from_row(self, index) {
Ok(v) => Ok(Some(v)),
Err(SqlxError::Sqlx(sqlx::Error::ColumnNotFound(_))) => Ok(None),
Err(SqlxError::Sqlx(sqlx::Error::ColumnDecode { ref source, .. }))
if source.is::<sqlx::error::UnexpectedNullError>() =>
{
Ok(None)
}
Err(e) => Err(e),
}
}
pub fn len(&self) -> usize {
match self {
#[cfg(feature = "postgres")]
Self::Postgres(row) => row.len(),
#[cfg(feature = "mysql")]
Self::MySql(row) => row.len(),
#[cfg(feature = "sqlite")]
Self::Sqlite(row) => row.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_json(&self) -> SqlxResult<JsonValue> {
match self {
#[cfg(feature = "postgres")]
Self::Postgres(row) => row_to_json_pg(row),
#[cfg(feature = "mysql")]
Self::MySql(row) => row_to_json_mysql(row),
#[cfg(feature = "sqlite")]
Self::Sqlite(row) => row_to_json_sqlite(row),
}
}
}
pub trait SqlxDecode: Sized {
fn decode_from_row(row: &SqlxRow, index: usize) -> SqlxResult<Self>;
}
pub trait SqlxDecodeNamed: Sized {
fn decode_by_name(row: &SqlxRow, name: &str) -> SqlxResult<Self>;
}
macro_rules! impl_decode {
($ty:ty) => {
impl SqlxDecode for $ty {
fn decode_from_row(row: &SqlxRow, index: usize) -> SqlxResult<Self> {
match row {
#[cfg(feature = "postgres")]
SqlxRow::Postgres(r) => r.try_get(index).map_err(SqlxError::from),
#[cfg(feature = "mysql")]
SqlxRow::MySql(r) => r.try_get(index).map_err(SqlxError::from),
#[cfg(feature = "sqlite")]
SqlxRow::Sqlite(r) => r.try_get(index).map_err(SqlxError::from),
}
}
}
impl SqlxDecodeNamed for $ty {
fn decode_by_name(row: &SqlxRow, name: &str) -> SqlxResult<Self> {
match row {
#[cfg(feature = "postgres")]
SqlxRow::Postgres(r) => r.try_get(name).map_err(SqlxError::from),
#[cfg(feature = "mysql")]
SqlxRow::MySql(r) => r.try_get(name).map_err(SqlxError::from),
#[cfg(feature = "sqlite")]
SqlxRow::Sqlite(r) => r.try_get(name).map_err(SqlxError::from),
}
}
}
};
}
impl_decode!(i32);
impl_decode!(i64);
impl_decode!(f32);
impl_decode!(f64);
impl_decode!(bool);
impl_decode!(String);
impl_decode!(Vec<u8>);
fn f64_to_json(f: f64) -> JsonValue {
serde_json::Number::from_f64(f).map_or(JsonValue::Null, JsonValue::Number)
}
#[cfg(any(feature = "postgres", feature = "mysql"))]
fn decimal_to_json(d: rust_decimal::Decimal) -> JsonValue {
serde_json::to_value(d).unwrap_or(JsonValue::Null)
}
fn bytes_to_json(bytes: Vec<u8>) -> JsonValue {
use prax_query::base64::Engine as _;
JsonValue::String(prax_query::base64::engine::general_purpose::STANDARD.encode(bytes))
}
#[cfg(feature = "postgres")]
fn row_to_json_pg(row: &sqlx::postgres::PgRow) -> SqlxResult<JsonValue> {
use sqlx::Column;
let mut obj = serde_json::Map::new();
for (i, col) in row.columns().iter().enumerate() {
let name = col.name().to_string();
obj.insert(name, pg_cell_to_json(row, i));
}
Ok(JsonValue::Object(obj))
}
#[cfg(feature = "postgres")]
fn pg_cell_to_json(row: &sqlx::postgres::PgRow, i: usize) -> JsonValue {
if let Ok(Some(s)) = row.try_get::<Option<String>, _>(i) {
return JsonValue::String(s);
}
if let Ok(Some(b)) = row.try_get::<Option<bool>, _>(i) {
return JsonValue::Bool(b);
}
if let Ok(Some(n)) = row.try_get::<Option<i64>, _>(i) {
return JsonValue::Number(n.into());
}
if let Ok(Some(n)) = row.try_get::<Option<i32>, _>(i) {
return JsonValue::Number(n.into());
}
if let Ok(Some(n)) = row.try_get::<Option<i16>, _>(i) {
return JsonValue::Number(n.into());
}
if let Ok(Some(f)) = row.try_get::<Option<f64>, _>(i) {
return f64_to_json(f);
}
if let Ok(Some(f)) = row.try_get::<Option<f32>, _>(i) {
return f64_to_json(f64::from(f));
}
if let Ok(Some(d)) = row.try_get::<Option<rust_decimal::Decimal>, _>(i) {
return decimal_to_json(d);
}
if let Ok(Some(ts)) = row.try_get::<Option<chrono::DateTime<chrono::Utc>>, _>(i) {
return JsonValue::String(ts.to_rfc3339());
}
if let Ok(Some(ts)) = row.try_get::<Option<chrono::NaiveDateTime>, _>(i) {
return JsonValue::String(ts.to_string());
}
if let Ok(Some(d)) = row.try_get::<Option<chrono::NaiveDate>, _>(i) {
return JsonValue::String(d.to_string());
}
if let Ok(Some(t)) = row.try_get::<Option<chrono::NaiveTime>, _>(i) {
return JsonValue::String(t.to_string());
}
if let Ok(Some(u)) = row.try_get::<Option<uuid::Uuid>, _>(i) {
return JsonValue::String(u.to_string());
}
if let Ok(Some(j)) = row.try_get::<Option<JsonValue>, _>(i) {
return j;
}
if let Ok(Some(b)) = row.try_get::<Option<Vec<u8>>, _>(i) {
return bytes_to_json(b);
}
JsonValue::Null
}
#[cfg(feature = "mysql")]
fn row_to_json_mysql(row: &sqlx::mysql::MySqlRow) -> SqlxResult<JsonValue> {
use sqlx::Column;
let mut obj = serde_json::Map::new();
for (i, col) in row.columns().iter().enumerate() {
let name = col.name().to_string();
obj.insert(name, mysql_cell_to_json(row, i));
}
Ok(JsonValue::Object(obj))
}
#[cfg(feature = "mysql")]
fn mysql_cell_to_json(row: &sqlx::mysql::MySqlRow, i: usize) -> JsonValue {
if let Ok(Some(s)) = row.try_get::<Option<String>, _>(i) {
return JsonValue::String(s);
}
if let Ok(Some(b)) = row.try_get::<Option<bool>, _>(i) {
return JsonValue::Bool(b);
}
if let Ok(Some(n)) = row.try_get::<Option<i64>, _>(i) {
return JsonValue::Number(n.into());
}
if let Ok(Some(f)) = row.try_get::<Option<f64>, _>(i) {
return f64_to_json(f);
}
if let Ok(Some(d)) = row.try_get::<Option<rust_decimal::Decimal>, _>(i) {
return decimal_to_json(d);
}
if let Ok(Some(ts)) = row.try_get::<Option<chrono::DateTime<chrono::Utc>>, _>(i) {
return JsonValue::String(ts.to_rfc3339());
}
if let Ok(Some(ts)) = row.try_get::<Option<chrono::NaiveDateTime>, _>(i) {
return JsonValue::String(ts.to_string());
}
if let Ok(Some(d)) = row.try_get::<Option<chrono::NaiveDate>, _>(i) {
return JsonValue::String(d.to_string());
}
if let Ok(Some(t)) = row.try_get::<Option<chrono::NaiveTime>, _>(i) {
return JsonValue::String(t.to_string());
}
if let Ok(Some(j)) = row.try_get::<Option<JsonValue>, _>(i) {
return j;
}
if let Ok(Some(b)) = row.try_get::<Option<Vec<u8>>, _>(i) {
return bytes_to_json(b);
}
JsonValue::Null
}
#[cfg(feature = "sqlite")]
fn row_to_json_sqlite(row: &sqlx::sqlite::SqliteRow) -> SqlxResult<JsonValue> {
use sqlx::Column;
let mut obj = serde_json::Map::new();
for (i, col) in row.columns().iter().enumerate() {
let name = col.name().to_string();
obj.insert(name, sqlite_cell_to_json(row, i));
}
Ok(JsonValue::Object(obj))
}
#[cfg(feature = "sqlite")]
fn sqlite_cell_to_json(row: &sqlx::sqlite::SqliteRow, i: usize) -> JsonValue {
if let Ok(Some(s)) = row.try_get::<Option<String>, _>(i) {
return JsonValue::String(s);
}
if let Ok(Some(n)) = row.try_get::<Option<i64>, _>(i) {
return JsonValue::Number(n.into());
}
if let Ok(Some(f)) = row.try_get::<Option<f64>, _>(i) {
return f64_to_json(f);
}
if let Ok(Some(b)) = row.try_get::<Option<bool>, _>(i) {
return JsonValue::Bool(b);
}
if let Ok(Some(b)) = row.try_get::<Option<Vec<u8>>, _>(i) {
return bytes_to_json(b);
}
JsonValue::Null
}
pub trait FromSqlxRow: Sized {
fn from_row(row: SqlxRow) -> SqlxResult<Self>;
}