Merge pull request #1311 from SeaQL/select-into-tuple

Select into tuple
This commit is contained in:
Chris Tsang 2023-01-10 15:20:14 +08:00 committed by GitHub
commit 4210526ec1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 465 additions and 127 deletions

View File

@ -352,8 +352,8 @@ impl ActiveEnum {
#[automatically_derived]
impl sea_orm::TryGetable for #ident {
fn try_get(res: &sea_orm::QueryResult, pre: &str, col: &str) -> std::result::Result<Self, sea_orm::TryGetError> {
let value = <<Self as sea_orm::ActiveEnum>::Value as sea_orm::TryGetable>::try_get(res, pre, col)?;
fn try_get_by<I: sea_orm::ColIdx>(res: &sea_orm::QueryResult, idx: I) -> std::result::Result<Self, sea_orm::TryGetError> {
let value = <<Self as sea_orm::ActiveEnum>::Value as sea_orm::TryGetable>::try_get_by(res, idx)?;
<Self as sea_orm::ActiveEnum>::try_from_value(&value).map_err(sea_orm::TryGetError::DbErr)
}
}

View File

@ -203,12 +203,25 @@ impl MockDatabaseTrait for MockDatabase {
}
impl MockRow {
/// Try to get the values of a [MockRow] and fail gracefully on error
pub fn try_get<T>(&self, col: &str) -> Result<T, DbErr>
/// Get a value from the [MockRow]
pub fn try_get<T, I: crate::ColIdx>(&self, index: I) -> Result<T, DbErr>
where
T: ValueType,
{
T::try_from(self.values.get(col).unwrap().clone()).map_err(|e| DbErr::Type(e.to_string()))
if let Some(index) = index.as_str() {
T::try_from(self.values.get(index).unwrap().clone())
.map_err(|e| DbErr::Type(e.to_string()))
} else if let Some(index) = index.as_usize() {
let (_, value) = self.values.iter().nth(*index).ok_or_else(|| {
DbErr::Query(RuntimeErr::Internal(format!(
"Column at index {} not found",
index
)))
})?;
T::try_from(value.clone()).map_err(|e| DbErr::Type(e.to_string()))
} else {
unreachable!("Missing ColIdx implementation for MockRow");
}
}
/// An iterator over the keys and values of a mock row
@ -686,10 +699,10 @@ mod tests {
);
let mocked_row = row.into_mock_row();
let a_id = mocked_row.try_get::<i32>("A_id");
let a_id = mocked_row.try_get::<i32, _>("A_id");
assert!(a_id.is_ok());
assert_eq!(1, a_id.unwrap());
let b_id = mocked_row.try_get::<i32>("B_id");
let b_id = mocked_row.try_get::<i32, _>("B_id");
assert!(b_id.is_ok());
assert_eq!(2, b_id.unwrap());
}

View File

@ -275,7 +275,7 @@ impl DatabaseTransaction {
if let Err(sqlx::Error::RowNotFound) = err {
Ok(None)
} else {
err.map_err(|e| sqlx_error_to_query_err(e))
err.map_err(sqlx_error_to_query_err)
}
}
}

View File

@ -152,8 +152,8 @@ where
T: ActiveEnum,
T::ValueVec: TryGetable,
{
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
<T::ValueVec as TryGetable>::try_get(res, pre, col)?
fn try_get_by<I: crate::ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
<T::ValueVec as TryGetable>::try_get_by(res, index)?
.into_iter()
.map(|value| T::try_from_value(&value).map_err(TryGetError::DbErr))
.collect()

View File

@ -21,10 +21,21 @@ 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
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError>;
/// Get a value from the query result with an ColIdx
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError>;
/// Get a value from the query result with prefixed column name
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let index = format!("{}{}", pre, col);
Self::try_get_by(res, index.as_str())
}
/// Get a value from the query result based on the order in the select expressions
fn try_get_by_index(res: &QueryResult, index: usize) -> Result<Self, TryGetError> {
Self::try_get_by(res, index)
}
}
/// An error from trying to get a row from a Model
@ -50,7 +61,16 @@ impl From<TryGetError> for DbErr {
// QueryResult //
impl QueryResult {
/// Get a Row from a Column
/// Get a value from the query result with an ColIdx
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)?)
}
/// Get a value from the query result with prefixed column name
pub fn try_get<T>(&self, pre: &str, col: &str) -> Result<T, DbErr>
where
T: TryGetable,
@ -58,13 +78,29 @@ impl QueryResult {
Ok(T::try_get(self, pre, col)?)
}
/// Perform query operations on multiple Columns
/// Get a value from the query result based on the order in the select expressions
pub fn try_get_by_index<T>(&self, idx: usize) -> Result<T, DbErr>
where
T: TryGetable,
{
Ok(T::try_get_by_index(self, idx)?)
}
/// Get a tuple value from the query result with prefixed column name
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)?)
}
/// Get a tuple value from the query result based on the order in the select expressions
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)]
@ -88,8 +124,8 @@ impl fmt::Debug for QueryResultRow {
// TryGetable //
impl<T: TryGetable> TryGetable for Option<T> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
match T::try_get(res, pre, col) {
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),
Err(e) => Err(e),
@ -97,39 +133,133 @@ impl<T: TryGetable> TryGetable for Option<T> {
}
}
/// Column Index, used by [`TryGetable`]
pub trait ColIdx: std::fmt::Debug + Copy {
#[cfg(feature = "sqlx-mysql")]
/// Type shenanigans
type SqlxMySqlIndex: sqlx::ColumnIndex<sqlx::mysql::MySqlRow>;
#[cfg(feature = "sqlx-postgres")]
/// Type shenanigans
type SqlxPostgresIndex: sqlx::ColumnIndex<sqlx::postgres::PgRow>;
#[cfg(feature = "sqlx-sqlite")]
/// Type shenanigans
type SqlxSqliteIndex: sqlx::ColumnIndex<sqlx::sqlite::SqliteRow>;
#[cfg(feature = "sqlx-mysql")]
/// Basically a no-op; only to satisfy a trait bound
fn as_sqlx_mysql_index(&self) -> Self::SqlxMySqlIndex;
#[cfg(feature = "sqlx-postgres")]
/// Basically a no-op; only to satisfy a trait bound
fn as_sqlx_postgres_index(&self) -> Self::SqlxPostgresIndex;
#[cfg(feature = "sqlx-sqlite")]
/// Basically a no-op; only to satisfy a trait bound
fn as_sqlx_sqlite_index(&self) -> Self::SqlxSqliteIndex;
/// Self must be `&str`, return `None` otherwise
fn as_str(&self) -> Option<&str>;
/// Self must be `usize`, return `None` otherwise
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 ) => {
#[allow(unused_variables)]
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let column = format!("{}{}", pre, col);
#[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 sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
row.try_get::<Option<$type>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
row.try_get::<Option<$type>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
row.try_get::<Option<$type>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -142,16 +272,15 @@ macro_rules! try_getable_all {
macro_rules! try_getable_unsigned {
( $type: ty ) => {
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
#[allow(unused_variables)]
let column = format!("{}{}", pre, col);
#[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 sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
row.try_get::<Option<$type>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
@ -160,15 +289,14 @@ macro_rules! try_getable_unsigned {
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
row.try_get::<Option<$type>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -181,16 +309,15 @@ macro_rules! try_getable_unsigned {
macro_rules! try_getable_mysql {
( $type: ty ) => {
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
#[allow(unused_variables)]
let column = format!("{}{}", pre, col);
#[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 sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
row.try_get::<Option<$type>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
@ -201,10 +328,9 @@ macro_rules! try_getable_mysql {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -218,40 +344,38 @@ macro_rules! try_getable_mysql {
macro_rules! try_getable_date_time {
( $type: ty ) => {
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
#[allow(unused_variables)]
let column = format!("{}{}", pre, col);
#[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};
use sqlx::Row;
row.try_get::<Option<DateTime<Utc>>, _>(column.as_str())
row.try_get::<Option<DateTime<Utc>>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|v| v.into())
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<$type>, _>(column.as_str())
row.try_get::<Option<$type>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| 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>>, _>(column.as_str())
row.try_get::<Option<DateTime<Utc>>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|v| v.into())
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -313,28 +437,27 @@ use rust_decimal::Decimal;
#[cfg(feature = "with-rust_decimal")]
impl TryGetable for Decimal {
#[allow(unused_variables)]
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let column = format!("{}{}", pre, col);
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<Decimal>, _>(column.as_str())
row.try_get::<Option<Decimal>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<Decimal>, _>(column.as_str())
row.try_get::<Option<Decimal>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
let val: Option<f64> = row
.try_get(column.as_str())
.try_get(idx.as_sqlx_sqlite_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
match val {
Some(v) => Decimal::try_from(v).map_err(|e| {
@ -344,14 +467,14 @@ impl TryGetable for Decimal {
source: Box::new(e),
})
}),
None => Err(TryGetError::Null(column)),
None => Err(err_null_idx_col(idx)),
}
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -365,28 +488,27 @@ use bigdecimal::BigDecimal;
#[cfg(feature = "with-bigdecimal")]
impl TryGetable for BigDecimal {
#[allow(unused_variables)]
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let column = format!("{}{}", pre, col);
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<BigDecimal>, _>(column.as_str())
row.try_get::<Option<BigDecimal>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get::<Option<BigDecimal>, _>(column.as_str())
row.try_get::<Option<BigDecimal>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
let val: Option<f64> = row
.try_get(column.as_str())
.try_get(idx.as_sqlx_sqlite_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))?;
match val {
Some(v) => BigDecimal::try_from(v).map_err(|e| {
@ -396,14 +518,14 @@ impl TryGetable for BigDecimal {
source: Box::new(e),
})
}),
None => Err(TryGetError::Null(column)),
None => Err(err_null_idx_col(idx)),
}
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -415,16 +537,15 @@ impl TryGetable for BigDecimal {
try_getable_all!(uuid::Uuid);
impl TryGetable for u32 {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
#[allow(unused_variables)]
let column = format!("{}{}", pre, col);
#[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 sqlx::Row;
row.try_get::<Option<u32>, _>(column.as_str())
row.try_get::<Option<u32>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
@ -432,23 +553,23 @@ impl TryGetable for u32 {
// 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>, _>(column.as_str())
row.try_get::<Option<Oid>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
.map(|oid| oid.0)
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get::<Option<u32>, _>(column.as_str())
row.try_get::<Option<u32>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)))
}
#[cfg(feature = "mock")]
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -456,6 +577,11 @@ impl TryGetable for u32 {
}
}
#[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::*;
@ -463,30 +589,30 @@ mod postgres_array {
#[allow(unused_macros)]
macro_rules! try_getable_postgres_array {
( $type: ty ) => {
#[allow(unused_variables)]
impl TryGetable for Vec<$type> {
#[allow(unused_variables)]
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let column = format!("{}{}", pre, col);
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
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>>, _>(column.as_str())
row.try_get::<Option<Vec<$type>>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.and_then(|opt| opt.ok_or_else(|| 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(column.as_str()).map_err(|e| {
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -549,11 +675,10 @@ mod postgres_array {
impl TryGetable for Vec<u32> {
#[allow(unused_variables)]
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let column = format!("{}{}", pre, col);
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
QueryResultRow::SqlxMySql(_) => {
panic!("{} unsupported by sqlx-mysql", stringify!($type))
}
#[cfg(feature = "sqlx-postgres")]
@ -562,9 +687,9 @@ mod postgres_array {
// 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>>, _>(column.as_str())
row.try_get::<Option<Vec<Oid>>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)))
.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")]
@ -572,9 +697,10 @@ mod postgres_array {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| {
#[allow(unused_variables)]
QueryResultRow::Mock(row) => row.try_get(idx).map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
}),
#[allow(unreachable_patterns)]
_ => unreachable!(),
@ -585,11 +711,14 @@ mod postgres_array {
// 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<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::*, *};
/// #
@ -663,6 +792,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<Self, TryGetError> {
T::try_get_by_index(res, 0)
}
}
impl<T> TryGetableMany for (T,)
@ -672,6 +805,10 @@ where
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,))
}
}
impl<A, B> TryGetableMany for (A, B)
@ -686,6 +823,10 @@ where
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)
@ -702,6 +843,14 @@ where
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)
@ -720,6 +869,15 @@ where
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)
@ -740,6 +898,16 @@ where
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)
@ -762,6 +930,17 @@ where
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> {
@ -778,44 +957,43 @@ fn try_get_many_with_slice_len_of(len: usize, cols: &[String]) -> Result<(), Try
// TryGetableFromJson //
/// Perform a query on multiple columns
/// An interface to get a JSON from the query result
#[cfg(feature = "with-json")]
pub trait TryGetableFromJson: Sized
where
for<'de> Self: serde::Deserialize<'de>,
{
/// Ensure the type implements this method
/// Get a JSON from the query result with prefixed column name
#[allow(unused_variables, unreachable_code)]
fn try_get_from_json(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let column = format!("{}{}", pre, col);
fn try_get_from_json<I: ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get::<Option<sqlx::types::Json<Self>>, _>(column.as_str())
row.try_get::<Option<sqlx::types::Json<Self>>, _>(idx.as_sqlx_mysql_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)).map(|json| json.0))
.and_then(|opt| opt.ok_or_else(|| 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>>, _>(column.as_str())
row.try_get::<Option<sqlx::types::Json<Self>>, _>(idx.as_sqlx_postgres_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)).map(|json| json.0))
.and_then(|opt| opt.ok_or_else(|| 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>>, _>(column.as_str())
row.try_get::<Option<sqlx::types::Json<Self>>, _>(idx.as_sqlx_sqlite_index())
.map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e)))
.and_then(|opt| opt.ok_or(TryGetError::Null(column)).map(|json| json.0))
.and_then(|opt| opt.ok_or_else(|| err_null_idx_col(idx)).map(|json| json.0))
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => row
.try_get::<serde_json::Value>(column.as_str())
.try_get::<serde_json::Value, I>(idx)
.map_err(|e| {
debug_print!("{:#?}", e.to_string());
TryGetError::Null(column)
err_null_idx_col(idx)
})
.and_then(|json| {
serde_json::from_value(json)
@ -832,8 +1010,8 @@ impl<T> TryGetable for T
where
T: TryGetableFromJson,
{
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
T::try_get_from_json(res, pre, col)
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
T::try_get_from_json(res, index)
}
}

View File

@ -41,7 +41,7 @@ pub trait SelectorTrait {
fn from_raw_query_result(res: QueryResult) -> Result<Self::Item, DbErr>;
}
/// Perform an operation on an entity that can yield a Value
/// Get tuple from query result based on a list of column identifiers
#[derive(Debug)]
pub struct SelectGetableValue<T, C>
where
@ -52,6 +52,15 @@ where
model: PhantomData<T>,
}
/// Get tuple from query result based on column index
#[derive(Debug)]
pub struct SelectGetableTuple<T>
where
T: TryGetableMany,
{
model: PhantomData<T>,
}
/// Defines a type to get a Model
#[derive(Debug)]
pub struct SelectModel<M>
@ -84,6 +93,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>
where
M: FromQueryResult + Sized,
@ -253,6 +273,104 @@ where
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
pub async fn one<'a, C>(self, db: &C) -> Result<Option<E::Model>, DbErr>
where
@ -402,7 +520,7 @@ impl<S> Selector<S>
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<T, C>(query: SelectStatement) -> Selector<SelectGetableValue<T, C>>
where
@ -418,6 +536,17 @@ where
}
}
/// Get tuple from query result based on column index
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>
where
C: ConnectionTrait,

View File

@ -76,5 +76,23 @@ pub async fn create_and_update(db: &DatabaseConnection) -> Result<(), DbErr> {
})
);
assert_eq!(
Entity::find()
.filter(Column::Id.eq(vec![1_u8, 2_u8, 3_u8])) // annotate it as Vec<u8> explicitly
.into_values::<_, Column>()
.one(db)
.await?,
Some((vec![1_u8, 2_u8, 3_u8], "First Row (Updated)".to_owned(),))
);
assert_eq!(
Entity::find()
.filter(Column::Id.eq(vec![1_u8, 2_u8, 3_u8])) // annotate it as Vec<u8> explicitly
.into_tuple()
.one(db)
.await?,
Some((vec![1_u8, 2_u8, 3_u8], "First Row (Updated)".to_owned(),))
);
Ok(())
}

View File

@ -39,8 +39,8 @@ impl From<Events> for Value {
}
impl TryGetable for Events {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let vec: Vec<String> = res.try_get(pre, col).map_err(TryGetError::DbErr)?;
fn try_get_by<I: sea_orm::ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
let vec: Vec<String> = res.try_get_by(idx).map_err(TryGetError::DbErr)?;
Ok(Events(vec.into_iter().map(Event).collect()))
}
}

View File

@ -25,8 +25,8 @@ impl From<StringVec> for Value {
}
impl TryGetable for StringVec {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
let json_str: String = res.try_get(pre, col).map_err(TryGetError::DbErr)?;
fn try_get_by<I: sea_orm::ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
let json_str: String = res.try_get_by(idx).map_err(TryGetError::DbErr)?;
serde_json::from_str(&json_str).map_err(|e| TryGetError::DbErr(DbErr::Json(e.to_string())))
}
}