pub use crate::error::TryGetError;
use crate::{
SelectGetableValue, SelectorRaw, Statement,
error::{DbErr, type_err},
};
use std::{
collections::{BTreeMap, HashMap},
fmt::Debug,
hash::Hash,
marker::PhantomData,
sync::Arc,
};
#[cfg(any(feature = "mock", feature = "proxy"))]
use crate::debug_print;
#[cfg(feature = "sqlx-dep")]
use crate::driver::*;
#[cfg(feature = "sqlx-dep")]
use sqlx::{Row, TypeInfo, ValueRef};
#[derive(Debug)]
pub struct QueryResult {
pub(crate) row: QueryResultRow,
}
#[allow(clippy::enum_variant_names)]
pub(crate) enum QueryResultRow {
#[cfg(feature = "sqlx-mysql")]
SqlxMySql(sqlx::mysql::MySqlRow),
#[cfg(feature = "sqlx-postgres")]
SqlxPostgres(sqlx::postgres::PgRow),
#[cfg(feature = "sqlx-sqlite")]
SqlxSqlite(sqlx::sqlite::SqliteRow),
#[cfg(feature = "rusqlite")]
Rusqlite(crate::driver::rusqlite::RusqliteRow),
#[cfg(feature = "mock")]
Mock(crate::MockRow),
#[cfg(feature = "proxy")]
Proxy(crate::ProxyRow),
}
pub trait TryGetable: Sized {
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError>;
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
if pre.is_empty() {
Self::try_get_by(res, col)
} else {
Self::try_get_by(res, format!("{pre}{col}").as_str())
}
}
fn try_get_by_index(res: &QueryResult, index: usize) -> Result<Self, TryGetError> {
Self::try_get_by(res, index)
}
}
impl From<TryGetError> for DbErr {
fn from(e: TryGetError) -> DbErr {
match e {
TryGetError::DbErr(e) => e,
TryGetError::Null(s) => {
type_err(format!("A null value was encountered while decoding {s}"))
}
}
}
}
impl From<DbErr> for TryGetError {
fn from(e: DbErr) -> TryGetError {
Self::DbErr(e)
}
}
impl QueryResult {
#[doc(hidden)]
#[cfg(feature = "sqlx-postgres")]
pub fn try_get_from_sqlx_postgres<T, I>(&self, idx: I) -> Option<Result<T, TryGetError>>
where
T: sqlx::Type<sqlx::Postgres> + for<'r> sqlx::Decode<'r, sqlx::Postgres>,
I: ColIdx,
{
match &self.row {
QueryResultRow::SqlxPostgres(row) => {
let value = match row.try_get_raw(idx.as_sqlx_postgres_index()) {
Ok(value) => value,
Err(err) => return Some(Err(sqlx_error_to_query_err(err).into())),
};
if !value.is_null() {
let ty = value.type_info();
if !ty.is_null() && !T::compatible(&ty) {
return None;
}
}
Some(
row.try_get::<Option<T>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
)
}
_ => None,
}
}
pub fn try_get_by<T, I>(&self, index: I) -> Result<T, DbErr>
where
T: TryGetable,
I: ColIdx,
{
Ok(T::try_get_by(self, index)?)
}
pub fn try_get_by_nullable<T, I>(&self, index: I) -> Result<T, TryGetError>
where
T: TryGetable,
I: ColIdx,
{
T::try_get_by(self, index)
}
pub fn try_get<T>(&self, pre: &str, col: &str) -> Result<T, DbErr>
where
T: TryGetable,
{
Ok(T::try_get(self, pre, col)?)
}
pub fn try_get_nullable<T>(&self, pre: &str, col: &str) -> Result<T, TryGetError>
where
T: TryGetable,
{
T::try_get(self, pre, col)
}
pub fn try_get_by_index<T>(&self, idx: usize) -> Result<T, DbErr>
where
T: TryGetable,
{
Ok(T::try_get_by_index(self, idx)?)
}
pub fn try_get_by_index_nullable<T>(&self, idx: usize) -> Result<T, TryGetError>
where
T: TryGetable,
{
T::try_get_by_index(self, idx)
}
pub fn try_get_many<T>(&self, pre: &str, cols: &[String]) -> Result<T, DbErr>
where
T: TryGetableMany,
{
Ok(T::try_get_many(self, pre, cols)?)
}
pub fn try_get_many_by_index<T>(&self) -> Result<T, DbErr>
where
T: TryGetableMany,
{
Ok(T::try_get_many_by_index(self)?)
}
pub fn column_names(&self) -> Vec<String> {
#[cfg(feature = "sqlx-dep")]
use sqlx::Column;
match &self.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
row.columns().iter().map(|c| c.name().to_string()).collect()
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
row.columns().iter().map(|c| c.name().to_string()).collect()
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
row.columns().iter().map(|c| c.name().to_string()).collect()
}
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => row.columns().iter().map(|c| c.to_string()).collect(),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row
.clone()
.into_column_value_tuples()
.map(|(c, _)| c.to_string())
.collect(),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row
.clone()
.into_column_value_tuples()
.map(|(c, _)| c.to_string())
.collect(),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
#[cfg(feature = "sqlx-mysql")]
pub fn try_as_mysql_row(&self) -> Option<&sqlx::mysql::MySqlRow> {
match &self.row {
QueryResultRow::SqlxMySql(mysql_row) => Some(mysql_row),
#[allow(unreachable_patterns)]
_ => None,
}
}
#[cfg(feature = "sqlx-postgres")]
pub fn try_as_pg_row(&self) -> Option<&sqlx::postgres::PgRow> {
match &self.row {
QueryResultRow::SqlxPostgres(pg_row) => Some(pg_row),
#[allow(unreachable_patterns)]
_ => None,
}
}
#[cfg(feature = "sqlx-sqlite")]
pub fn try_as_sqlite_row(&self) -> Option<&sqlx::sqlite::SqliteRow> {
match &self.row {
QueryResultRow::SqlxSqlite(sqlite_row) => Some(sqlite_row),
#[allow(unreachable_patterns)]
_ => None,
}
}
#[cfg(feature = "mock")]
pub fn try_as_mock_row(&self) -> Option<&crate::MockRow> {
match &self.row {
QueryResultRow::Mock(mock_row) => Some(mock_row),
#[allow(unreachable_patterns)]
_ => None,
}
}
#[cfg(feature = "proxy")]
pub fn try_as_proxy_row(&self) -> Option<&crate::ProxyRow> {
match &self.row {
QueryResultRow::Proxy(proxy_row) => Some(proxy_row),
#[allow(unreachable_patterns)]
_ => None,
}
}
}
#[allow(unused_variables)]
impl Debug for QueryResultRow {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
#[cfg(feature = "sqlx-mysql")]
Self::SqlxMySql(row) => write!(f, "{row:?}"),
#[cfg(feature = "sqlx-postgres")]
Self::SqlxPostgres(_) => write!(f, "QueryResultRow::SqlxPostgres cannot be inspected"),
#[cfg(feature = "sqlx-sqlite")]
Self::SqlxSqlite(_) => write!(f, "QueryResultRow::SqlxSqlite cannot be inspected"),
#[cfg(feature = "rusqlite")]
Self::Rusqlite(row) => write!(f, "{row:?}"),
#[cfg(feature = "mock")]
Self::Mock(row) => write!(f, "{row:?}"),
#[cfg(feature = "proxy")]
Self::Proxy(row) => write!(f, "{row:?}"),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
impl<T: TryGetable> TryGetable for Option<T> {
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
match T::try_get_by(res, index) {
Ok(v) => Ok(Some(v)),
Err(TryGetError::Null(_)) => Ok(None),
#[cfg(feature = "sqlx-dep")]
Err(TryGetError::DbErr(DbErr::Query(crate::RuntimeErr::SqlxError(err)))) => {
use std::ops::Deref;
match err.deref() {
sqlx::Error::ColumnNotFound(_) => Ok(None),
_ => Err(TryGetError::DbErr(DbErr::Query(
crate::RuntimeErr::SqlxError(err),
))),
}
}
Err(e) => Err(e),
}
}
}
pub trait ColIdx: Debug + Copy {
#[cfg(feature = "sqlx-mysql")]
type SqlxMySqlIndex: sqlx::ColumnIndex<sqlx::mysql::MySqlRow>;
#[cfg(feature = "sqlx-postgres")]
type SqlxPostgresIndex: sqlx::ColumnIndex<sqlx::postgres::PgRow>;
#[cfg(feature = "sqlx-sqlite")]
type SqlxSqliteIndex: sqlx::ColumnIndex<sqlx::sqlite::SqliteRow>;
#[cfg(feature = "sqlx-mysql")]
fn as_sqlx_mysql_index(&self) -> Self::SqlxMySqlIndex;
#[cfg(feature = "sqlx-postgres")]
fn as_sqlx_postgres_index(&self) -> Self::SqlxPostgresIndex;
#[cfg(feature = "sqlx-sqlite")]
fn as_sqlx_sqlite_index(&self) -> Self::SqlxSqliteIndex;
fn as_str(&self) -> Option<&str>;
fn as_usize(&self) -> Option<&usize>;
}
impl ColIdx for &str {
#[cfg(feature = "sqlx-mysql")]
type SqlxMySqlIndex = Self;
#[cfg(feature = "sqlx-postgres")]
type SqlxPostgresIndex = Self;
#[cfg(feature = "sqlx-sqlite")]
type SqlxSqliteIndex = Self;
#[cfg(feature = "sqlx-mysql")]
#[inline]
fn as_sqlx_mysql_index(&self) -> Self::SqlxMySqlIndex {
self
}
#[cfg(feature = "sqlx-postgres")]
#[inline]
fn as_sqlx_postgres_index(&self) -> Self::SqlxPostgresIndex {
self
}
#[cfg(feature = "sqlx-sqlite")]
#[inline]
fn as_sqlx_sqlite_index(&self) -> Self::SqlxSqliteIndex {
self
}
#[inline]
fn as_str(&self) -> Option<&str> {
Some(self)
}
#[inline]
fn as_usize(&self) -> Option<&usize> {
None
}
}
impl ColIdx for usize {
#[cfg(feature = "sqlx-mysql")]
type SqlxMySqlIndex = Self;
#[cfg(feature = "sqlx-postgres")]
type SqlxPostgresIndex = Self;
#[cfg(feature = "sqlx-sqlite")]
type SqlxSqliteIndex = Self;
#[cfg(feature = "sqlx-mysql")]
#[inline]
fn as_sqlx_mysql_index(&self) -> Self::SqlxMySqlIndex {
*self
}
#[cfg(feature = "sqlx-postgres")]
#[inline]
fn as_sqlx_postgres_index(&self) -> Self::SqlxPostgresIndex {
*self
}
#[cfg(feature = "sqlx-sqlite")]
#[inline]
fn as_sqlx_sqlite_index(&self) -> Self::SqlxSqliteIndex {
*self
}
#[inline]
fn as_str(&self) -> Option<&str> {
None
}
#[inline]
fn as_usize(&self) -> Option<&usize> {
Some(self)
}
}
macro_rules! try_getable_all {
( $type: ty ) => {
impl TryGetable for $type {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<$type>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<$type>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => row
.try_get::<Option<$type>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => row
.try_get::<Option<$type>, _>(idx)
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
};
}
macro_rules! try_getable_unsigned {
( $type: ty ) => {
impl TryGetable for $type {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<$type>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => Err(type_err(format!(
"{} unsupported by sqlx-postgres",
stringify!($type)
))
.into()),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => row
.try_get::<Option<$type>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => row
.try_get::<Option<$type>, _>(idx)
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
};
}
macro_rules! try_getable_mysql {
( $type: ty ) => {
impl TryGetable for $type {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<$type>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => Err(type_err(format!(
"{} unsupported by sqlx-postgres",
stringify!($type)
))
.into()),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => Err(type_err(format!(
"{} unsupported by sqlx-sqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => Err(type_err(format!(
"{} unsupported by rusqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
};
}
#[allow(unused_macros)]
macro_rules! try_getable_postgres {
( $type: ty ) => {
impl TryGetable for $type {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(_) => Err(type_err(format!(
"{} unsupported by sqlx-mysql",
stringify!($type)
))
.into()),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<$type>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => Err(type_err(format!(
"{} unsupported by sqlx-sqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => Err(type_err(format!(
"{} unsupported by rusqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
};
}
#[allow(unused_macros)]
macro_rules! try_getable_date_time {
( $type: ty ) => {
impl TryGetable for $type {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use chrono::{DateTime, Utc};
row.try_get::<Option<DateTime<Utc>>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|v| v.into())
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<$type>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use chrono::{DateTime, Utc};
row.try_get::<Option<DateTime<Utc>>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|v| v.into())
}
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => {
use chrono::{DateTime, Utc};
row.try_get::<Option<DateTime<Utc>>, _>(idx)
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|v| v.into())
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
};
}
try_getable_all!(bool);
try_getable_all!(i8);
try_getable_all!(i16);
try_getable_all!(i32);
try_getable_all!(i64);
try_getable_unsigned!(u8);
try_getable_unsigned!(u16);
try_getable_mysql!(u64);
try_getable_all!(f32);
try_getable_all!(f64);
try_getable_all!(Vec<u8>);
#[cfg(feature = "with-json")]
try_getable_all!(serde_json::Value);
#[cfg(feature = "with-chrono")]
try_getable_all!(chrono::NaiveDate);
#[cfg(feature = "with-chrono")]
try_getable_all!(chrono::NaiveTime);
#[cfg(feature = "with-chrono")]
try_getable_all!(chrono::NaiveDateTime);
#[cfg(feature = "with-chrono")]
try_getable_date_time!(chrono::DateTime<chrono::FixedOffset>);
#[cfg(feature = "with-chrono")]
try_getable_all!(chrono::DateTime<chrono::Utc>);
#[cfg(feature = "with-chrono")]
try_getable_all!(chrono::DateTime<chrono::Local>);
#[cfg(feature = "with-time")]
try_getable_all!(time::Date);
#[cfg(feature = "with-time")]
try_getable_all!(time::Time);
#[cfg(feature = "with-time")]
try_getable_all!(time::PrimitiveDateTime);
#[cfg(feature = "with-time")]
try_getable_all!(time::OffsetDateTime);
#[cfg(feature = "with-rust_decimal")]
use rust_decimal::Decimal;
#[cfg(feature = "with-rust_decimal")]
impl TryGetable for Decimal {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<Decimal>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<Decimal>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
let val: Option<f64> = row
.try_get(idx.as_sqlx_sqlite_index())
.map_err(sqlx_error_to_query_err)?;
match val {
Some(v) => Decimal::try_from(v).map_err(|e| {
DbErr::TryIntoErr {
from: "f64",
into: "Decimal",
source: Arc::new(e),
}
.into()
}),
None => Err(err_null_idx_col(idx)),
}
}
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => {
let val: Option<f64> = row.try_get(idx)?;
match val {
Some(v) => Decimal::try_from(v).map_err(|e| {
DbErr::TryIntoErr {
from: "f64",
into: "Decimal",
source: Arc::new(e),
}
.into()
}),
None => Err(err_null_idx_col(idx)),
}
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
#[allow(unused_variables)]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
#[cfg(feature = "with-bigdecimal")]
use bigdecimal::BigDecimal;
#[cfg(feature = "with-bigdecimal")]
impl TryGetable for BigDecimal {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<BigDecimal>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<BigDecimal>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
let val: Option<f64> = row
.try_get(idx.as_sqlx_sqlite_index())
.map_err(sqlx_error_to_query_err)?;
match val {
Some(v) => BigDecimal::try_from(v).map_err(|e| {
DbErr::TryIntoErr {
from: "f64",
into: "BigDecimal",
source: Arc::new(e),
}
.into()
}),
None => Err(err_null_idx_col(idx)),
}
}
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => {
let val: Option<f64> = row.try_get(idx)?;
match val {
Some(v) => BigDecimal::try_from(v).map_err(|e| {
DbErr::TryIntoErr {
from: "f64",
into: "BigDecimal",
source: Arc::new(e),
}
.into()
}),
None => Err(err_null_idx_col(idx)),
}
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
#[allow(unused_variables)]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
#[allow(unused_macros)]
macro_rules! try_getable_uuid {
( $type: ty, $conversion_fn: expr ) => {
#[allow(unused_variables, unreachable_code)]
impl TryGetable for $type {
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
let res: Result<uuid::Uuid, TryGetError> = match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<uuid::Uuid>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.or_else(|_| {
row.try_get::<Option<Vec<u8>>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|bytes| {
String::from_utf8(bytes).map_err(|e| {
DbErr::TryIntoErr {
from: "Vec<u8>",
into: "String",
source: Arc::new(e),
}
.into()
})
})?
.and_then(|s| {
uuid::Uuid::parse_str(&s).map_err(|e| {
DbErr::TryIntoErr {
from: "String",
into: "uuid::Uuid",
source: Arc::new(e),
}
.into()
})
})
}),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<uuid::Uuid>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => row
.try_get::<Option<uuid::Uuid>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => row
.try_get::<Option<uuid::Uuid>, _>(idx)
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get::<uuid::Uuid, _>(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
#[allow(unused_variables)]
QueryResultRow::Proxy(row) => row.try_get::<uuid::Uuid, _>(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
res.map($conversion_fn)
}
}
};
}
#[cfg(feature = "with-uuid")]
try_getable_uuid!(uuid::Uuid, Into::into);
#[cfg(feature = "with-uuid")]
try_getable_uuid!(uuid::fmt::Braced, uuid::Uuid::braced);
#[cfg(feature = "with-uuid")]
try_getable_uuid!(uuid::fmt::Hyphenated, uuid::Uuid::hyphenated);
#[cfg(feature = "with-uuid")]
try_getable_uuid!(uuid::fmt::Simple, uuid::Uuid::simple);
#[cfg(feature = "with-uuid")]
try_getable_uuid!(uuid::fmt::Urn, uuid::Uuid::urn);
#[cfg(feature = "with-ipnetwork")]
try_getable_postgres!(ipnetwork::IpNetwork);
#[cfg(feature = "with-mac_address")]
try_getable_postgres!(mac_address::MacAddress);
impl TryGetable for u32 {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<u32>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::postgres::types::Oid;
match row.try_get::<Option<Oid>, _>(idx.as_sqlx_postgres_index()) {
Ok(opt) => opt.ok_or_else(|| err_null_idx_col(idx)).map(|oid| oid.0),
Err(_) => row
.try_get::<i32, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.map(|v| {
v.try_into().map_err(|e| {
DbErr::TryIntoErr {
from: "i32",
into: "u32",
source: Arc::new(e),
}
.into()
})
})
.and_then(|r| r),
}
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => row
.try_get::<Option<u32>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => row
.try_get::<Option<u32>, _>(idx)
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
#[allow(unused_variables)]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
impl TryGetable for String {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<Vec<u8>>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|bytes| {
String::from_utf8(bytes).map_err(|e| {
DbErr::TryIntoErr {
from: "Vec<u8>",
into: "String",
source: Arc::new(e),
}
.into()
})
})?,
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<String>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => row
.try_get::<Option<String>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => row
.try_get::<Option<String>, _>(idx)
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
#[allow(dead_code)]
fn err_null_idx_col<I: ColIdx>(idx: I) -> TryGetError {
TryGetError::Null(format!("{idx:?}"))
}
#[cfg(feature = "postgres-array")]
mod postgres_array {
use super::*;
#[allow(unused_macros)]
macro_rules! try_getable_postgres_array {
( $type: ty ) => {
#[allow(unused_variables)]
impl TryGetable for Vec<$type> {
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(_) => Err(type_err(format!(
"{} unsupported by sqlx-mysql",
stringify!($type)
))
.into()),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<Vec<$type>>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => Err(type_err(format!(
"{} unsupported by sqlx-sqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(_) => Err(type_err(format!(
"{} unsupported by rusqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
#[allow(unused_variables)]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
};
}
try_getable_postgres_array!(bool);
try_getable_postgres_array!(i8);
try_getable_postgres_array!(i16);
try_getable_postgres_array!(i32);
try_getable_postgres_array!(i64);
try_getable_postgres_array!(f32);
try_getable_postgres_array!(f64);
try_getable_postgres_array!(String);
try_getable_postgres_array!(Vec<u8>);
#[cfg(feature = "with-json")]
try_getable_postgres_array!(serde_json::Value);
#[cfg(feature = "with-chrono")]
try_getable_postgres_array!(chrono::NaiveDate);
#[cfg(feature = "with-chrono")]
try_getable_postgres_array!(chrono::NaiveTime);
#[cfg(feature = "with-chrono")]
try_getable_postgres_array!(chrono::NaiveDateTime);
#[cfg(feature = "with-chrono")]
try_getable_postgres_array!(chrono::DateTime<chrono::FixedOffset>);
#[cfg(feature = "with-chrono")]
try_getable_postgres_array!(chrono::DateTime<chrono::Utc>);
#[cfg(feature = "with-chrono")]
try_getable_postgres_array!(chrono::DateTime<chrono::Local>);
#[cfg(feature = "with-time")]
try_getable_postgres_array!(time::Date);
#[cfg(feature = "with-time")]
try_getable_postgres_array!(time::Time);
#[cfg(feature = "with-time")]
try_getable_postgres_array!(time::PrimitiveDateTime);
#[cfg(feature = "with-time")]
try_getable_postgres_array!(time::OffsetDateTime);
#[cfg(feature = "with-rust_decimal")]
try_getable_postgres_array!(rust_decimal::Decimal);
#[cfg(feature = "with-bigdecimal")]
try_getable_postgres_array!(bigdecimal::BigDecimal);
#[cfg(feature = "with-ipnetwork")]
try_getable_postgres_array!(ipnetwork::IpNetwork);
#[cfg(feature = "with-mac_address")]
try_getable_postgres_array!(mac_address::MacAddress);
#[allow(unused_macros)]
macro_rules! try_getable_postgres_array_uuid {
( $type: ty, $conversion_fn: expr ) => {
#[allow(unused_variables, unreachable_code)]
impl TryGetable for Vec<$type> {
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
let res: Result<Vec<uuid::Uuid>, TryGetError> = match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(_) => Err(type_err(format!(
"{} unsupported by sqlx-mysql",
stringify!($type)
))
.into()),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<Vec<uuid::Uuid>>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => Err(type_err(format!(
"{} unsupported by sqlx-sqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(_) => Err(type_err(format!(
"{} unsupported by rusqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => {
row.try_get::<Vec<uuid::Uuid>, _>(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
})
}
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => {
row.try_get::<Vec<uuid::Uuid>, _>(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
})
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
res.map(|vec| vec.into_iter().map($conversion_fn).collect())
}
}
};
}
#[cfg(feature = "with-uuid")]
try_getable_postgres_array_uuid!(uuid::Uuid, Into::into);
#[cfg(feature = "with-uuid")]
try_getable_postgres_array_uuid!(uuid::fmt::Braced, uuid::Uuid::braced);
#[cfg(feature = "with-uuid")]
try_getable_postgres_array_uuid!(uuid::fmt::Hyphenated, uuid::Uuid::hyphenated);
#[cfg(feature = "with-uuid")]
try_getable_postgres_array_uuid!(uuid::fmt::Simple, uuid::Uuid::simple);
#[cfg(feature = "with-uuid")]
try_getable_postgres_array_uuid!(uuid::fmt::Urn, uuid::Uuid::urn);
impl TryGetable for Vec<u32> {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(_) => {
Err(type_err(format!("{} unsupported by sqlx-mysql", stringify!($type))).into())
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::postgres::types::Oid;
row.try_get::<Option<Vec<Oid>>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|oids| oids.into_iter().map(|oid| oid.0).collect())
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => Err(type_err(format!(
"{} unsupported by sqlx-sqlite",
stringify!($type)
))
.into()),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(_) => {
Err(type_err(format!("{} unsupported by rusqlite", stringify!($type))).into())
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
#[allow(unused_variables)]
QueryResultRow::Proxy(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
}
#[cfg(feature = "postgres-vector")]
impl TryGetable for pgvector::Vector {
#[allow(unused_variables)]
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(_) => {
Err(type_err("Vector unsupported by sqlx-mysql").into())
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<pgvector::Vector>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx))),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => {
Err(type_err("Vector unsupported by sqlx-sqlite").into())
}
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(_) => Err(type_err("Vector unsupported by rusqlite").into()),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get::<pgvector::Vector, _>(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row.try_get::<pgvector::Vector, _>(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
pub trait TryGetableMany: Sized {
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError>;
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError>;
fn find_by_statement<C>(stmt: Statement) -> SelectorRaw<SelectGetableValue<Self, C>>
where
C: strum::IntoEnumIterator + sea_query::Iden,
{
SelectorRaw {
stmt,
selector: PhantomData,
}
}
}
impl<T> TryGetableMany for T
where
T: TryGetable,
{
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> {
try_get_many_with_slice_len_of(1, cols)?;
T::try_get(res, pre, &cols[0])
}
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
T::try_get_by_index(res, 0)
}
}
impl<T> TryGetableMany for (T,)
where
T: TryGetableMany,
{
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> {
T::try_get_many(res, pre, cols).map(|r| (r,))
}
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
T::try_get_many_by_index(res).map(|r| (r,))
}
}
macro_rules! impl_try_get_many {
( $LEN:expr, $($T:ident : $N:expr),+ $(,)? ) => {
impl< $($T),+ > TryGetableMany for ( $($T),+ )
where
$($T: TryGetable),+
{
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> {
try_get_many_with_slice_len_of($LEN, cols)?;
Ok((
$($T::try_get(res, pre, &cols[$N])?),+
))
}
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
Ok((
$($T::try_get_by_index(res, $N)?),+
))
}
}
};
}
#[rustfmt::skip]
mod impl_try_get_many {
use super::*;
impl_try_get_many!( 2, T0:0, T1:1);
impl_try_get_many!( 3, T0:0, T1:1, T2:2);
impl_try_get_many!( 4, T0:0, T1:1, T2:2, T3:3);
impl_try_get_many!( 5, T0:0, T1:1, T2:2, T3:3, T4:4);
impl_try_get_many!( 6, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5);
impl_try_get_many!( 7, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6);
impl_try_get_many!( 8, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7);
impl_try_get_many!( 9, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8);
impl_try_get_many!(10, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9);
impl_try_get_many!(11, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10);
impl_try_get_many!(12, T0:0, T1:1, T2:2, T3:3, T4:4, T5:5, T6:6, T7:7, T8:8, T9:9, T10:10, T11:11);
}
fn try_get_many_with_slice_len_of(len: usize, cols: &[String]) -> Result<(), TryGetError> {
if cols.len() < len {
Err(type_err(format!(
"Expect {} column names supplied but got slice of length {}",
len,
cols.len()
))
.into())
} else {
Ok(())
}
}
pub trait TryGetableArray: Sized {
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Vec<Self>, TryGetError>;
}
impl<T> TryGetable for Vec<T>
where
T: TryGetableArray,
{
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
T::try_get_by(res, index)
}
}
#[cfg(feature = "with-json")]
pub trait TryGetableFromJson: Sized
where
for<'de> Self: serde::Deserialize<'de>,
{
#[allow(unused_variables, unreachable_code)]
fn try_get_from_json<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => row
.try_get::<Option<sqlx::types::Json<Self>>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)).map(|json| json.0)),
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => row
.try_get::<Option<sqlx::types::Json<Self>>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)).map(|json| json.0)),
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => row
.try_get::<Option<sqlx::types::Json<Self>>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| sqlx_error_to_query_err(e).into())
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)).map(|json| json.0)),
#[cfg(feature = "rusqlite")]
QueryResultRow::Rusqlite(row) => row
.try_get::<Option<serde_json::Value>, _>(idx)?
.ok_or_else(|| err_null_idx_col(idx))
.and_then(|json| {
serde_json::from_value(json).map_err(|e| crate::error::json_err(e).into())
}),
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row
.try_get::<serde_json::Value, I>(idx)
.map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
})
.and_then(|json| {
serde_json::from_value(json).map_err(|e| crate::error::json_err(e).into())
}),
#[cfg(feature = "proxy")]
QueryResultRow::Proxy(row) => row
.try_get::<serde_json::Value, I>(idx)
.map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
})
.and_then(|json| {
serde_json::from_value(json).map_err(|e| crate::error::json_err(e).into())
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
fn from_json_vec(value: serde_json::Value) -> Result<Vec<Self>, TryGetError> {
match value {
serde_json::Value::Array(values) => {
let mut res = Vec::new();
for item in values {
res.push(serde_json::from_value(item).map_err(crate::error::json_err)?);
}
Ok(res)
}
_ => Err(TryGetError::DbErr(DbErr::Json(
"Value is not an Array".to_owned(),
))),
}
}
}
#[cfg(feature = "with-json")]
impl<T> TryGetable for T
where
T: TryGetableFromJson,
{
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
T::try_get_from_json(res, index)
}
}
#[cfg(feature = "with-json")]
impl<T> TryGetableArray for T
where
T: TryGetableFromJson,
{
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Vec<T>, TryGetError> {
T::from_json_vec(serde_json::Value::try_get_by(res, index)?)
}
}
pub trait TryFromU64: Sized {
fn try_from_u64(n: u64) -> Result<Self, DbErr>;
}
#[cfg(feature = "with-json")]
use serde::de::DeserializeOwned;
#[cfg(feature = "with-json")]
impl<K, V> TryGetableFromJson for HashMap<K, V>
where
K: DeserializeOwned + Eq + Hash,
V: DeserializeOwned,
{
}
#[cfg(feature = "with-json")]
impl<K, V> TryGetableFromJson for BTreeMap<K, V>
where
K: DeserializeOwned + Ord,
V: DeserializeOwned,
{
}
macro_rules! try_from_u64_err {
( $type: ty ) => {
impl TryFromU64 for $type {
fn try_from_u64(_: u64) -> Result<Self, DbErr> {
Err(DbErr::ConvertFromU64(stringify!($type)))
}
}
};
( $($gen_type: ident),* ) => {
impl<$( $gen_type, )*> TryFromU64 for ($( $gen_type, )*)
where
$( $gen_type: TryFromU64, )*
{
fn try_from_u64(_: u64) -> Result<Self, DbErr> {
Err(DbErr::ConvertFromU64(stringify!($($gen_type,)*)))
}
}
};
}
#[rustfmt::skip]
mod try_from_u64_err {
use super::*;
try_from_u64_err!(T0, T1);
try_from_u64_err!(T0, T1, T2);
try_from_u64_err!(T0, T1, T2, T3);
try_from_u64_err!(T0, T1, T2, T3, T4);
try_from_u64_err!(T0, T1, T2, T3, T4, T5);
try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6);
try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7);
try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
try_from_u64_err!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
}
macro_rules! try_from_u64_numeric {
( $type: ty ) => {
impl TryFromU64 for $type {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
use std::convert::TryInto;
n.try_into().map_err(|e| DbErr::TryIntoErr {
from: stringify!(u64),
into: stringify!($type),
source: Arc::new(e),
})
}
}
};
}
try_from_u64_numeric!(i8);
try_from_u64_numeric!(i16);
try_from_u64_numeric!(i32);
try_from_u64_numeric!(i64);
try_from_u64_numeric!(u8);
try_from_u64_numeric!(u16);
try_from_u64_numeric!(u32);
try_from_u64_numeric!(u64);
macro_rules! try_from_u64_string {
( $type: ty ) => {
impl TryFromU64 for $type {
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
Ok(n.to_string())
}
}
};
}
try_from_u64_string!(String);
try_from_u64_err!(bool);
try_from_u64_err!(f32);
try_from_u64_err!(f64);
try_from_u64_err!(Vec<u8>);
#[cfg(feature = "with-json")]
try_from_u64_err!(serde_json::Value);
#[cfg(feature = "with-chrono")]
try_from_u64_err!(chrono::NaiveDate);
#[cfg(feature = "with-chrono")]
try_from_u64_err!(chrono::NaiveTime);
#[cfg(feature = "with-chrono")]
try_from_u64_err!(chrono::NaiveDateTime);
#[cfg(feature = "with-chrono")]
try_from_u64_err!(chrono::DateTime<chrono::FixedOffset>);
#[cfg(feature = "with-chrono")]
try_from_u64_err!(chrono::DateTime<chrono::Utc>);
#[cfg(feature = "with-chrono")]
try_from_u64_err!(chrono::DateTime<chrono::Local>);
#[cfg(feature = "with-time")]
try_from_u64_err!(time::Date);
#[cfg(feature = "with-time")]
try_from_u64_err!(time::Time);
#[cfg(feature = "with-time")]
try_from_u64_err!(time::PrimitiveDateTime);
#[cfg(feature = "with-time")]
try_from_u64_err!(time::OffsetDateTime);
#[cfg(feature = "with-rust_decimal")]
try_from_u64_err!(rust_decimal::Decimal);
#[cfg(feature = "with-uuid")]
try_from_u64_err!(uuid::Uuid);
#[cfg(feature = "with-ipnetwork")]
try_from_u64_err!(ipnetwork::IpNetwork);
#[cfg(feature = "with-mac_address")]
try_from_u64_err!(mac_address::MacAddress);
#[cfg(test)]
mod tests {
use super::*;
use crate::{MockRow, RuntimeErr};
use sea_query::Value;
use crate::{QueryResult, TryGetable};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
#[test]
fn from_try_get_error() {
let try_get_error = TryGetError::DbErr(DbErr::Query(RuntimeErr::Internal(
"expected error message".to_owned(),
)));
assert_eq!(
DbErr::from(try_get_error),
DbErr::Query(RuntimeErr::Internal("expected error message".to_owned()))
);
let try_get_error = TryGetError::Null("column".to_owned());
let expected = "A null value was encountered while decoding column".to_owned();
assert_eq!(DbErr::from(try_get_error), DbErr::Type(expected));
}
#[test]
fn build_with_query() {
use sea_orm::{DbBackend, Statement};
use sea_query::{
ColumnRef, CommonTableExpression, Cycle, Expr, ExprTrait, JoinType, SelectStatement,
UnionType, WithClause,
};
let base_query = SelectStatement::new()
.column("id")
.expr(1i32)
.column("next")
.column("value")
.from("table")
.to_owned();
let cte_referencing = SelectStatement::new()
.column("id")
.expr(Expr::col("depth").add(1i32))
.column("next")
.column("value")
.from("table")
.join(
JoinType::InnerJoin,
"cte_traversal",
Expr::col(("cte_traversal", "next")).equals(("table", "id")),
)
.to_owned();
let common_table_expression = CommonTableExpression::new()
.query(
base_query
.clone()
.union(UnionType::All, cte_referencing)
.to_owned(),
)
.columns(["id", "depth", "next", "value"])
.table_name("cte_traversal")
.to_owned();
let select = SelectStatement::new()
.column(ColumnRef::Asterisk(None))
.from("cte_traversal")
.to_owned();
let with_clause = WithClause::new()
.recursive(true)
.cte(common_table_expression)
.cycle(Cycle::new_from_expr_set_using(
Expr::column("id"),
"looped",
"traversal_path",
))
.to_owned();
let with_query = select.with(with_clause).to_owned();
assert_eq!(
DbBackend::MySql.build(&with_query),
Statement::from_sql_and_values(
DbBackend::MySql,
r"WITH RECURSIVE `cte_traversal` (`id`, `depth`, `next`, `value`) AS (SELECT `id`, ?, `next`, `value` FROM `table` UNION ALL (SELECT `id`, `depth` + ?, `next`, `value` FROM `table` INNER JOIN `cte_traversal` ON `cte_traversal`.`next` = `table`.`id`)) SELECT * FROM `cte_traversal`",
[1.into(), 1.into()]
)
);
}
#[test]
fn column_names_from_query_result() {
let mut values = BTreeMap::new();
values.insert("id".to_string(), Value::Int(Some(1)));
values.insert("name".to_string(), Value::String(Some("Abc".to_owned())));
let query_result = QueryResult {
row: QueryResultRow::Mock(MockRow { values }),
};
assert_eq!(
query_result.column_names(),
vec!["id".to_owned(), "name".to_owned()]
);
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct Component {
base_price: i32,
component_type: String,
}
#[test]
fn json_deserialize_to_btreemap() {
let json_value = serde_json::json!({
"engine": {
"base_price": 100,
"component_type": "metal"
}
});
let values = BTreeMap::from([(
"components".to_string(),
Value::Json(Some(Box::new(json_value))),
)]);
let row = QueryResultRow::Mock(MockRow { values });
let result: BTreeMap<String, Component> =
TryGetable::try_get_by(&QueryResult { row }, "components").unwrap();
assert_eq!(
result.get("engine"),
Some(&Component {
base_price: 100,
component_type: "metal".to_owned(),
})
);
}
#[test]
fn json_deserialize_to_hashmap() {
let json_value = serde_json::json!({
"engine": {
"base_price": 100,
"component_type": "metal"
}
});
let values = BTreeMap::from([(
"components".to_string(),
Value::Json(Some(Box::new(json_value))),
)]);
let row = QueryResultRow::Mock(MockRow { values });
let result: HashMap<String, Component> =
TryGetable::try_get_by(&QueryResult { row }, "components").unwrap();
assert_eq!(
result.get("engine"),
Some(&Component {
base_price: 100,
component_type: "metal".to_owned(),
})
);
}
}