From 70c4a3a23e96077a184da06cbff21a5b6cd96ce6 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 15 Dec 2022 21:24:10 +0800 Subject: [PATCH] Select into tuple --- sea-orm-macros/src/derives/active_enum.rs | 5 + src/database/mock.rs | 15 + src/entity/active_enum.rs | 7 + src/executor/query.rs | 452 +++++++++++++++++++++- src/executor/select.rs | 129 +++++- tests/common/features/json_vec.rs | 5 + 6 files changed, 606 insertions(+), 7 deletions(-) diff --git a/sea-orm-macros/src/derives/active_enum.rs b/sea-orm-macros/src/derives/active_enum.rs index 5f2d9a65..aa5fc7d8 100644 --- a/sea-orm-macros/src/derives/active_enum.rs +++ b/sea-orm-macros/src/derives/active_enum.rs @@ -356,6 +356,11 @@ impl ActiveEnum { let value = <::Value as sea_orm::TryGetable>::try_get(res, pre, col)?; ::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr) } + + fn try_get_by_index(res: &sea_orm::QueryResult, idx: usize) -> std::result::Result { + let value = <::Value as sea_orm::TryGetable>::try_get_by_index(res, idx)?; + ::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr) + } } #[automatically_derived] diff --git a/src/database/mock.rs b/src/database/mock.rs index 4a3640ab..207abd7f 100644 --- a/src/database/mock.rs +++ b/src/database/mock.rs @@ -211,6 +211,21 @@ impl MockRow { T::try_from(self.values.get(col).unwrap().clone()).map_err(|e| DbErr::Type(e.to_string())) } + pub fn try_get_by_index(&self, idx: usize) -> Result + 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 pub fn into_column_value_tuples(self) -> impl Iterator { self.values.into_iter() diff --git a/src/entity/active_enum.rs b/src/entity/active_enum.rs index c6a8feff..6fdf676a 100644 --- a/src/entity/active_enum.rs +++ b/src/entity/active_enum.rs @@ -158,6 +158,13 @@ where .map(|value| T::try_from_value(&value).map_err(TryGetError::DbErr)) .collect() } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + ::try_get_by_index(res, idx)? + .into_iter() + .map(|value| T::try_from_value(&value).map_err(TryGetError::DbErr)) + .collect() + } } #[cfg(test)] diff --git a/src/executor/query.rs b/src/executor/query.rs index 025674de..fb9cec06 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -21,10 +21,13 @@ pub(crate) enum QueryResultRow { 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 { - /// 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; + + /// 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; } /// An error from trying to get a row from a Model @@ -58,6 +61,13 @@ impl QueryResult { Ok(T::try_get(self, pre, col)?) } + pub fn try_get_by_index(&self, idx: usize) -> Result + where + T: TryGetable, + { + Ok(T::try_get_by_index(self, idx)?) + } + /// Perform query operations on multiple Columns pub fn try_get_many(&self, pre: &str, cols: &[String]) -> Result where @@ -65,6 +75,13 @@ impl QueryResult { { Ok(T::try_get_many(self, pre, cols)?) } + + pub fn try_get_many_by_index(&self) -> Result + where + T: TryGetableMany, + { + Ok(T::try_get_many_by_index(self)?) + } } #[allow(unused_variables)] @@ -95,6 +112,14 @@ impl TryGetable for Option { Err(e) => Err(e), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + 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 { @@ -135,6 +160,40 @@ macro_rules! try_getable_all { _ => unreachable!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + row.try_get::, _>(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::, _>(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::, _>(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!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + row.try_get::, _>(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::, _>(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!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + row.try_get::, _>(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!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use chrono::{DateTime, Utc}; + use sqlx::Row; + row.try_get::>, _>(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::, _>(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::>, _>(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!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + row.try_get::, _>(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::, _>(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 = 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")] @@ -409,6 +609,50 @@ impl TryGetable for BigDecimal { _ => unreachable!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + row.try_get::, _>(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::, _>(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 = 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")] @@ -454,6 +698,48 @@ impl TryGetable for u32 { _ => unreachable!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + row.try_get::, _>(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::, _>(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::, _>(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")] @@ -469,7 +755,7 @@ mod postgres_array { let column = format!("{}{}", pre, col); match &res.row { #[cfg(feature = "sqlx-mysql")] - QueryResultRow::SqlxMySql(row) => { + QueryResultRow::SqlxMySql(_) => { panic!("{} unsupported by sqlx-mysql", stringify!($type)) } #[cfg(feature = "sqlx-postgres")] @@ -492,6 +778,33 @@ mod postgres_array { _ => unreachable!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + 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::>, _>(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); match &res.row { #[cfg(feature = "sqlx-mysql")] - QueryResultRow::SqlxMySql(row) => { + QueryResultRow::SqlxMySql(_) => { panic!("{} unsupported by sqlx-mysql", stringify!($type)) } #[cfg(feature = "sqlx-postgres")] @@ -580,16 +893,50 @@ mod postgres_array { _ => unreachable!(), } } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + 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::>, _>(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 // -/// Perform a query on multiple columns +/// An interface to get a tuple value from the query result 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; + /// 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; + /// ``` /// # use sea_orm::{error::*, tests_cfg::*, *}; /// # @@ -663,6 +1010,10 @@ where 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 { + T::try_get_by_index(res, 0) + } } impl TryGetableMany for (T,) @@ -672,6 +1023,10 @@ where fn try_get_many(res: &QueryResult, pre: &str, cols: &[String]) -> Result { T::try_get_many(res, pre, cols).map(|r| (r,)) } + + fn try_get_many_by_index(res: &QueryResult) -> Result { + T::try_get_many_by_index(res).map(|r| (r,)) + } } impl TryGetableMany for (A, B) @@ -686,6 +1041,10 @@ where B::try_get(res, pre, &cols[1])?, )) } + + fn try_get_many_by_index(res: &QueryResult) -> Result { + Ok((A::try_get_by_index(res, 0)?, B::try_get_by_index(res, 1)?)) + } } impl TryGetableMany for (A, B, C) @@ -702,6 +1061,14 @@ where C::try_get(res, pre, &cols[2])?, )) } + + fn try_get_many_by_index(res: &QueryResult) -> Result { + Ok(( + A::try_get_by_index(res, 0)?, + B::try_get_by_index(res, 1)?, + C::try_get_by_index(res, 2)?, + )) + } } impl TryGetableMany for (A, B, C, D) @@ -720,6 +1087,15 @@ where D::try_get(res, pre, &cols[3])?, )) } + + fn try_get_many_by_index(res: &QueryResult) -> Result { + 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 TryGetableMany for (A, B, C, D, E) @@ -740,6 +1116,16 @@ where E::try_get(res, pre, &cols[4])?, )) } + + fn try_get_many_by_index(res: &QueryResult) -> Result { + 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 TryGetableMany for (A, B, C, D, E, F) @@ -762,6 +1148,17 @@ where F::try_get(res, pre, &cols[5])?, )) } + + fn try_get_many_by_index(res: &QueryResult) -> Result { + 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> { @@ -825,6 +1222,45 @@ where _ => unreachable!(), } } + + fn try_get_from_json_by_index(res: &QueryResult, idx: usize) -> Result { + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + row.try_get::>, _>(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::>, _>(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::>, _>(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::(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")] @@ -835,6 +1271,10 @@ where fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { T::try_get_from_json(res, pre, col) } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + T::try_get_from_json_by_index(res, idx) + } } // TryFromU64 // diff --git a/src/executor/select.rs b/src/executor/select.rs index a31b2a83..df24e938 100644 --- a/src/executor/select.rs +++ b/src/executor/select.rs @@ -52,6 +52,14 @@ where model: PhantomData, } +#[derive(Debug)] +pub struct SelectGetableTuple +where + T: TryGetableMany, +{ + model: PhantomData, +} + /// Defines a type to get a Model #[derive(Debug)] pub struct SelectModel @@ -84,6 +92,17 @@ where } } +impl SelectorTrait for SelectGetableTuple +where + T: TryGetableMany, +{ + type Item = T; + + fn from_raw_query_result(res: QueryResult) -> Result { + T::try_get_many_by_index(&res).map_err(Into::into) + } +} + impl SelectorTrait for SelectModel where M: FromQueryResult + Sized, @@ -253,6 +272,104 @@ where Selector::>::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::::into("Chocolate Forest"), + /// # }, + /// # maplit::btreemap! { + /// # "cake_name" => Into::::into("New York Cheese"), + /// # }, + /// # ]]) + /// # .into_connection(); + /// # + /// use sea_orm::{entity::*, query::*, tests_cfg::cake}; + /// + /// let res: Vec = 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::::into("Chocolate Forest"), + /// # "num_of_cakes" => Into::::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(self) -> Selector> + where + T: TryGetableMany, + { + Selector::>::into_tuple(self.query) + } + /// Get one Model from the SELECT query pub async fn one<'a, C>(self, db: &C) -> Result, DbErr> where @@ -402,7 +519,7 @@ impl Selector where 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`. pub fn with_columns(query: SelectStatement) -> Selector> where @@ -418,6 +535,16 @@ where } } + pub fn into_tuple(query: SelectStatement) -> Selector> + where + T: TryGetableMany, + { + Selector { + query, + selector: SelectGetableTuple { model: PhantomData }, + } + } + fn into_selector_raw(self, db: &C) -> SelectorRaw where C: ConnectionTrait, diff --git a/tests/common/features/json_vec.rs b/tests/common/features/json_vec.rs index 8e9aca5b..82cfec5a 100644 --- a/tests/common/features/json_vec.rs +++ b/tests/common/features/json_vec.rs @@ -29,6 +29,11 @@ impl TryGetable for StringVec { 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()))) } + + fn try_get_by_index(res: &QueryResult, idx: usize) -> Result { + 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 {