Select into tuple

This commit is contained in:
Billy Chan 2022-12-15 21:24:10 +08:00
parent 5b9c49a9a0
commit 70c4a3a23e
No known key found for this signature in database
GPG Key ID: A2D690CAC7DF3CC7
6 changed files with 606 additions and 7 deletions

View File

@ -356,6 +356,11 @@ impl ActiveEnum {
let value = <<Self as sea_orm::ActiveEnum>::Value as sea_orm::TryGetable>::try_get(res, pre, col)?; let value = <<Self as sea_orm::ActiveEnum>::Value as sea_orm::TryGetable>::try_get(res, pre, col)?;
<Self as sea_orm::ActiveEnum>::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr) <Self as sea_orm::ActiveEnum>::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr)
} }
fn try_get_by_index(res: &sea_orm::QueryResult, idx: usize) -> std::result::Result<Self, sea_orm::TryGetError> {
let value = <<Self as sea_orm::ActiveEnum>::Value as sea_orm::TryGetable>::try_get_by_index(res, idx)?;
<Self as sea_orm::ActiveEnum>::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr)
}
} }
#[automatically_derived] #[automatically_derived]

View File

@ -211,6 +211,21 @@ impl MockRow {
T::try_from(self.values.get(col).unwrap().clone()).map_err(|e| DbErr::Type(e.to_string())) T::try_from(self.values.get(col).unwrap().clone()).map_err(|e| DbErr::Type(e.to_string()))
} }
pub fn try_get_by_index<T>(&self, idx: usize) -> Result<T, DbErr>
where
T: ValueType,
{
let (_, value) = self
.values
.iter()
.nth(idx)
.ok_or(DbErr::Query(RuntimeErr::Internal(format!(
"Column at index {} not found",
idx
))))?;
T::try_from(value.clone()).map_err(|e| DbErr::Type(e.to_string()))
}
/// An iterator over the keys and values of a mock row /// An iterator over the keys and values of a mock row
pub fn into_column_value_tuples(self) -> impl Iterator<Item = (String, Value)> { pub fn into_column_value_tuples(self) -> impl Iterator<Item = (String, Value)> {
self.values.into_iter() self.values.into_iter()

View File

@ -158,6 +158,13 @@ where
.map(|value| T::try_from_value(&value).map_err(TryGetError::DbErr)) .map(|value| T::try_from_value(&value).map_err(TryGetError::DbErr))
.collect() .collect()
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
<T::ValueVec as TryGetable>::try_get_by_index(res, idx)?
.into_iter()
.map(|value| T::try_from_value(&value).map_err(TryGetError::DbErr))
.collect()
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -21,10 +21,13 @@ pub(crate) enum QueryResultRow {
Mock(crate::MockRow), Mock(crate::MockRow),
} }
/// Constrain any type trying to get a Row in a database /// An interface to get a value from the query result
pub trait TryGetable: Sized { pub trait TryGetable: Sized {
/// Ensure the type implements this method /// Get a value from the query result with prefixed column name
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError>; fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError>;
/// Get a value from the query result based on the order in the select expressions
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError>;
} }
/// An error from trying to get a row from a Model /// An error from trying to get a row from a Model
@ -58,6 +61,13 @@ impl QueryResult {
Ok(T::try_get(self, pre, col)?) Ok(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)?)
}
/// Perform query operations on multiple Columns /// Perform query operations on multiple Columns
pub fn try_get_many<T>(&self, pre: &str, cols: &[String]) -> Result<T, DbErr> pub fn try_get_many<T>(&self, pre: &str, cols: &[String]) -> Result<T, DbErr>
where where
@ -65,6 +75,13 @@ impl QueryResult {
{ {
Ok(T::try_get_many(self, pre, cols)?) 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)?)
}
} }
#[allow(unused_variables)] #[allow(unused_variables)]
@ -95,6 +112,14 @@ impl<T: TryGetable> TryGetable for Option<T> {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match T::try_get_by_index(res, idx) {
Ok(v) => Ok(Some(v)),
Err(TryGetError::Null(_)) => Ok(None),
Err(e) => Err(e),
}
}
} }
macro_rules! try_getable_all { macro_rules! try_getable_all {
@ -135,6 +160,40 @@ macro_rules! try_getable_all {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(idx.to_string())
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
}; };
} }
@ -174,6 +233,37 @@ macro_rules! try_getable_unsigned {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
panic!("{} unsupported by sqlx-postgres", stringify!($type))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
}; };
} }
@ -210,6 +300,34 @@ macro_rules! try_getable_mysql {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
panic!("{} unsupported by sqlx-postgres", stringify!($type))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
}; };
} }
@ -257,6 +375,44 @@ macro_rules! try_getable_date_time {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use chrono::{DateTime, Utc};
use sqlx::Row;
row.try_get::<Option<DateTime<Utc>>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
.map(|v| v.into())
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use chrono::{DateTime, Utc};
use sqlx::Row;
row.try_get::<Option<DateTime<Utc>>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
.map(|v| v.into())
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
}; };
} }
@ -357,6 +513,50 @@ impl TryGetable for Decimal {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<Decimal>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<Decimal>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
let val: Option<f64> = row
.try_get(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
match val {
Some(v) => Decimal::try_from(v).map_err(|e| {
TryGetError::DbErr(DbErr::TryIntoErr {
from: "f64",
into: "Decimal",
source: Box::new(e),
})
}),
None => Err(err_null_idx_col(idx)),
}
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
#[cfg(feature = "with-bigdecimal")] #[cfg(feature = "with-bigdecimal")]
@ -409,6 +609,50 @@ impl TryGetable for BigDecimal {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<BigDecimal>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<BigDecimal>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
let val: Option<f64> = row
.try_get(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
match val {
Some(v) => BigDecimal::try_from(v).map_err(|e| {
TryGetError::DbErr(DbErr::TryIntoErr {
from: "f64",
into: "BigDecimal",
source: Box::new(e),
})
}),
None => Err(err_null_idx_col(idx)),
}
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
#[cfg(feature = "with-uuid")] #[cfg(feature = "with-uuid")]
@ -454,6 +698,48 @@ impl TryGetable for u32 {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<u32>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::postgres::types::Oid;
// Since 0.6.0, SQLx has dropped direct mapping from PostgreSQL's OID to Rust's `u32`;
// Instead, `u32` was wrapped by a `sqlx::Oid`.
use sqlx::Row;
row.try_get::<Option<Oid>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
.map(|oid| oid.0)
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<u32>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
}
fn err_null_idx_col(idx: usize) -> TryGetError {
TryGetError::Null(format!("column at index {}", idx))
} }
#[cfg(feature = "postgres-array")] #[cfg(feature = "postgres-array")]
@ -469,7 +755,7 @@ mod postgres_array {
let column = format!("{}{}", pre, col); let column = format!("{}{}", pre, col);
match &res.row { match &res.row {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(_) => {
panic!("{} unsupported by sqlx-mysql", stringify!($type)) panic!("{} unsupported by sqlx-mysql", stringify!($type))
} }
#[cfg(feature = "sqlx-postgres")] #[cfg(feature = "sqlx-postgres")]
@ -492,6 +778,33 @@ mod postgres_array {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(_) => {
panic!("{} unsupported by sqlx-mysql", stringify!($type))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<Vec<$type>>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
}; };
} }
@ -553,7 +866,7 @@ mod postgres_array {
let column = format!("{}{}", pre, col); let column = format!("{}{}", pre, col);
match &res.row { match &res.row {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => { QueryResultRow::SqlxMySql(_) => {
panic!("{} unsupported by sqlx-mysql", stringify!($type)) panic!("{} unsupported by sqlx-mysql", stringify!($type))
} }
#[cfg(feature = "sqlx-postgres")] #[cfg(feature = "sqlx-postgres")]
@ -580,16 +893,50 @@ mod postgres_array {
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(_) => {
panic!("{} unsupported by sqlx-mysql", stringify!($type))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::postgres::types::Oid;
// Since 0.6.0, SQLx has dropped direct mapping from PostgreSQL's OID to Rust's `u32`;
// Instead, `u32` was wrapped by a `sqlx::Oid`.
use sqlx::Row;
row.try_get::<Option<Vec<Oid>>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)))
.map(|oids| oids.into_iter().map(|oid| oid.0).collect())
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get_by_index(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
} }
// TryGetableMany // // TryGetableMany //
/// Perform a query on multiple columns /// An interface to get a tuple value from the query result
pub trait TryGetableMany: Sized { pub trait TryGetableMany: Sized {
/// THe method to perform a query on multiple columns /// Get a tuple value from the query result with prefixed column name
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError>; fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError>;
/// Get a tuple value from the query result based on the order in the select expressions
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError>;
/// ``` /// ```
/// # use sea_orm::{error::*, tests_cfg::*, *}; /// # use sea_orm::{error::*, tests_cfg::*, *};
/// # /// #
@ -663,6 +1010,10 @@ where
try_get_many_with_slice_len_of(1, cols)?; try_get_many_with_slice_len_of(1, cols)?;
T::try_get(res, pre, &cols[0]) 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,) impl<T> TryGetableMany for (T,)
@ -672,6 +1023,10 @@ where
fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> { fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result<Self, TryGetError> {
T::try_get_many(res, pre, cols).map(|r| (r,)) 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,))
}
} }
impl<A, B> TryGetableMany for (A, B) impl<A, B> TryGetableMany for (A, B)
@ -686,6 +1041,10 @@ where
B::try_get(res, pre, &cols[1])?, B::try_get(res, pre, &cols[1])?,
)) ))
} }
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
Ok((A::try_get_by_index(res, 0)?, B::try_get_by_index(res, 1)?))
}
} }
impl<A, B, C> TryGetableMany for (A, B, C) impl<A, B, C> TryGetableMany for (A, B, C)
@ -702,6 +1061,14 @@ where
C::try_get(res, pre, &cols[2])?, C::try_get(res, pre, &cols[2])?,
)) ))
} }
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
Ok((
A::try_get_by_index(res, 0)?,
B::try_get_by_index(res, 1)?,
C::try_get_by_index(res, 2)?,
))
}
} }
impl<A, B, C, D> TryGetableMany for (A, B, C, D) impl<A, B, C, D> TryGetableMany for (A, B, C, D)
@ -720,6 +1087,15 @@ where
D::try_get(res, pre, &cols[3])?, D::try_get(res, pre, &cols[3])?,
)) ))
} }
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
Ok((
A::try_get_by_index(res, 0)?,
B::try_get_by_index(res, 1)?,
C::try_get_by_index(res, 2)?,
D::try_get_by_index(res, 3)?,
))
}
} }
impl<A, B, C, D, E> TryGetableMany for (A, B, C, D, E) impl<A, B, C, D, E> TryGetableMany for (A, B, C, D, E)
@ -740,6 +1116,16 @@ where
E::try_get(res, pre, &cols[4])?, E::try_get(res, pre, &cols[4])?,
)) ))
} }
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
Ok((
A::try_get_by_index(res, 0)?,
B::try_get_by_index(res, 1)?,
C::try_get_by_index(res, 2)?,
D::try_get_by_index(res, 3)?,
E::try_get_by_index(res, 4)?,
))
}
} }
impl<A, B, C, D, E, F> TryGetableMany for (A, B, C, D, E, F) impl<A, B, C, D, E, F> TryGetableMany for (A, B, C, D, E, F)
@ -762,6 +1148,17 @@ where
F::try_get(res, pre, &cols[5])?, F::try_get(res, pre, &cols[5])?,
)) ))
} }
fn try_get_many_by_index(res: &QueryResult) -> Result<Self, TryGetError> {
Ok((
A::try_get_by_index(res, 0)?,
B::try_get_by_index(res, 1)?,
C::try_get_by_index(res, 2)?,
D::try_get_by_index(res, 3)?,
E::try_get_by_index(res, 4)?,
F::try_get_by_index(res, 5)?,
))
}
} }
fn try_get_many_with_slice_len_of(len: usize, cols: &[String]) -> Result<(), TryGetError> { fn try_get_many_with_slice_len_of(len: usize, cols: &[String]) -> Result<(), TryGetError> {
@ -825,6 +1222,45 @@ where
_ => unreachable!(), _ => unreachable!(),
} }
} }
fn try_get_from_json_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<sqlx::types::Json<Self>>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)).map(|json| json.0))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<sqlx::types::Json<Self>>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)).map(|json| json.0))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<sqlx::types::Json<Self>>, _>(idx)
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(err_null_idx_col(idx)).map(|json| json.0))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row
.try_get_by_index::<serde_json::Value>(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| TryGetError::DbErr(DbErr::Json(e.to_string())))
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
}
}
} }
#[cfg(feature = "with-json")] #[cfg(feature = "with-json")]
@ -835,6 +1271,10 @@ where
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> { fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
T::try_get_from_json(res, pre, col) T::try_get_from_json(res, pre, col)
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
T::try_get_from_json_by_index(res, idx)
}
} }
// TryFromU64 // // TryFromU64 //

View File

@ -52,6 +52,14 @@ where
model: PhantomData<T>, model: PhantomData<T>,
} }
#[derive(Debug)]
pub struct SelectGetableTuple<T>
where
T: TryGetableMany,
{
model: PhantomData<T>,
}
/// Defines a type to get a Model /// Defines a type to get a Model
#[derive(Debug)] #[derive(Debug)]
pub struct SelectModel<M> pub struct SelectModel<M>
@ -84,6 +92,17 @@ where
} }
} }
impl<T> SelectorTrait for SelectGetableTuple<T>
where
T: TryGetableMany,
{
type Item = T;
fn from_raw_query_result(res: QueryResult) -> Result<Self::Item, DbErr> {
T::try_get_many_by_index(&res).map_err(Into::into)
}
}
impl<M> SelectorTrait for SelectModel<M> impl<M> SelectorTrait for SelectModel<M>
where where
M: FromQueryResult + Sized, M: FromQueryResult + Sized,
@ -253,6 +272,104 @@ where
Selector::<SelectGetableValue<T, C>>::with_columns(self.query) Selector::<SelectGetableValue<T, C>>::with_columns(self.query)
} }
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(all(feature = "mock", feature = "macros"))]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
/// # maplit::btreemap! {
/// # "cake_name" => Into::<Value>::into("Chocolate Forest"),
/// # },
/// # maplit::btreemap! {
/// # "cake_name" => Into::<Value>::into("New York Cheese"),
/// # },
/// # ]])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// let res: Vec<String> = cake::Entity::find()
/// .select_only()
/// .column(cake::Column::Name)
/// .into_tuple()
/// .all(&db)
/// .await?;
///
/// assert_eq!(
/// res,
/// vec!["Chocolate Forest".to_owned(), "New York Cheese".to_owned()]
/// );
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// r#"SELECT "cake"."name" FROM "cake""#,
/// vec![]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
///
/// ```
/// # use sea_orm::{error::*, tests_cfg::*, *};
/// #
/// # #[smol_potat::main]
/// # #[cfg(all(feature = "mock", feature = "macros"))]
/// # pub async fn main() -> Result<(), DbErr> {
/// #
/// # let db = MockDatabase::new(DbBackend::Postgres)
/// # .append_query_results(vec![vec![
/// # maplit::btreemap! {
/// # "cake_name" => Into::<Value>::into("Chocolate Forest"),
/// # "num_of_cakes" => Into::<Value>::into(2i64),
/// # },
/// # ]])
/// # .into_connection();
/// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake};
///
/// let res: Vec<(String, i64)> = cake::Entity::find()
/// .select_only()
/// .column(cake::Column::Name)
/// .column(cake::Column::Id)
/// .group_by(cake::Column::Name)
/// .into_tuple()
/// .all(&db)
/// .await?;
///
/// assert_eq!(res, vec![("Chocolate Forest".to_owned(), 2i64)]);
///
/// assert_eq!(
/// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres,
/// vec![
/// r#"SELECT "cake"."name", "cake"."id""#,
/// r#"FROM "cake" GROUP BY "cake"."name""#,
/// ]
/// .join(" ")
/// .as_str(),
/// vec![]
/// )]
/// );
/// #
/// # Ok(())
/// # }
/// ```
pub fn into_tuple<T>(self) -> Selector<SelectGetableTuple<T>>
where
T: TryGetableMany,
{
Selector::<SelectGetableTuple<T>>::into_tuple(self.query)
}
/// Get one Model from the SELECT query /// Get one Model from the SELECT query
pub async fn one<'a, C>(self, db: &C) -> Result<Option<E::Model>, DbErr> pub async fn one<'a, C>(self, db: &C) -> Result<Option<E::Model>, DbErr>
where where
@ -402,7 +519,7 @@ impl<S> Selector<S>
where where
S: SelectorTrait, S: SelectorTrait,
{ {
/// Create `Selector` from Statment and columns. Executing this `Selector` /// Create `Selector` from Statement and columns. Executing this `Selector`
/// will return a type `T` which implement `TryGetableMany`. /// will return a type `T` which implement `TryGetableMany`.
pub fn with_columns<T, C>(query: SelectStatement) -> Selector<SelectGetableValue<T, C>> pub fn with_columns<T, C>(query: SelectStatement) -> Selector<SelectGetableValue<T, C>>
where where
@ -418,6 +535,16 @@ where
} }
} }
pub fn into_tuple<T>(query: SelectStatement) -> Selector<SelectGetableTuple<T>>
where
T: TryGetableMany,
{
Selector {
query,
selector: SelectGetableTuple { model: PhantomData },
}
}
fn into_selector_raw<C>(self, db: &C) -> SelectorRaw<S> fn into_selector_raw<C>(self, db: &C) -> SelectorRaw<S>
where where
C: ConnectionTrait, C: ConnectionTrait,

View File

@ -29,6 +29,11 @@ impl TryGetable for StringVec {
let json_str: String = res.try_get(pre, col).map_err(TryGetError::DbErr)?; let json_str: String = res.try_get(pre, col).map_err(TryGetError::DbErr)?;
serde_json::from_str(&json_str).map_err(|e| TryGetError::DbErr(DbErr::Json(e.to_string()))) serde_json::from_str(&json_str).map_err(|e| TryGetError::DbErr(DbErr::Json(e.to_string())))
} }
fn try_get_by_index(res: &QueryResult, idx: usize) -> Result<Self, TryGetError> {
let json_str: String = res.try_get_by_index(idx).map_err(TryGetError::DbErr)?;
serde_json::from_str(&json_str).map_err(|e| TryGetError::DbErr(DbErr::Json(e.to_string())))
}
} }
impl sea_query::ValueType for StringVec { impl sea_query::ValueType for StringVec {