commit
28a5de09d6
@ -9,6 +9,7 @@ mod from_query_result;
|
|||||||
mod into_active_model;
|
mod into_active_model;
|
||||||
mod migration;
|
mod migration;
|
||||||
mod model;
|
mod model;
|
||||||
|
mod partial_model;
|
||||||
mod primary_key;
|
mod primary_key;
|
||||||
mod relation;
|
mod relation;
|
||||||
mod try_getable_from_json;
|
mod try_getable_from_json;
|
||||||
@ -24,6 +25,7 @@ pub use from_query_result::*;
|
|||||||
pub use into_active_model::*;
|
pub use into_active_model::*;
|
||||||
pub use migration::*;
|
pub use migration::*;
|
||||||
pub use model::*;
|
pub use model::*;
|
||||||
|
pub use partial_model::*;
|
||||||
pub use primary_key::*;
|
pub use primary_key::*;
|
||||||
pub use relation::*;
|
pub use relation::*;
|
||||||
pub use try_getable_from_json::*;
|
pub use try_getable_from_json::*;
|
||||||
|
269
sea-orm-macros/src/derives/partial_model.rs
Normal file
269
sea-orm-macros/src/derives/partial_model.rs
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
use heck::ToUpperCamelCase;
|
||||||
|
use proc_macro2::Span;
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::format_ident;
|
||||||
|
use quote::quote;
|
||||||
|
use quote::quote_spanned;
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
use syn::token::Comma;
|
||||||
|
use syn::Expr;
|
||||||
|
|
||||||
|
use syn::Meta;
|
||||||
|
|
||||||
|
use self::util::GetAsKVMeta;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Error {
|
||||||
|
InputNotStruct,
|
||||||
|
EntityNotSpecific,
|
||||||
|
NotSupportGeneric(Span),
|
||||||
|
BothFromColAndFromExpr(Span),
|
||||||
|
Syn(syn::Error),
|
||||||
|
}
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum ColumnAs {
|
||||||
|
/// column in the model
|
||||||
|
Col(syn::Ident),
|
||||||
|
/// alias from a column in model
|
||||||
|
ColAlias { col: syn::Ident, field: String },
|
||||||
|
/// from a expr
|
||||||
|
Expr { expr: syn::Expr, field_name: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DerivePartialModel {
|
||||||
|
entity_ident: Option<syn::Ident>,
|
||||||
|
ident: syn::Ident,
|
||||||
|
fields: Vec<ColumnAs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerivePartialModel {
|
||||||
|
fn new(input: syn::DeriveInput) -> Result<Self, Error> {
|
||||||
|
if !input.generics.params.is_empty() {
|
||||||
|
return Err(Error::NotSupportGeneric(input.generics.params.span()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let syn::Data::Struct(syn::DataStruct{fields:syn::Fields::Named(syn::FieldsNamed{named:fields,..}),..},..) = input.data else{
|
||||||
|
return Err(Error::InputNotStruct);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut entity_ident = None;
|
||||||
|
|
||||||
|
for attr in input.attrs.iter() {
|
||||||
|
if let Some(ident) = attr.path.get_ident() {
|
||||||
|
if ident != "sea_orm" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) {
|
||||||
|
for meta in list {
|
||||||
|
entity_ident = meta
|
||||||
|
.get_as_kv("entity")
|
||||||
|
.map(|s| syn::parse_str::<syn::Ident>(&s).map_err(Error::Syn))
|
||||||
|
.transpose()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut column_as_list = Vec::with_capacity(fields.len());
|
||||||
|
|
||||||
|
for field in fields {
|
||||||
|
let field_span = field.span();
|
||||||
|
|
||||||
|
let mut from_col = None;
|
||||||
|
let mut from_expr = None;
|
||||||
|
|
||||||
|
for attr in field.attrs.iter() {
|
||||||
|
if !attr.path.is_ident("sea_orm") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
|
||||||
|
{
|
||||||
|
for meta in list.iter() {
|
||||||
|
from_col = meta
|
||||||
|
.get_as_kv("from_col")
|
||||||
|
.map(|s| format_ident!("{}", s.to_upper_camel_case()));
|
||||||
|
from_expr = meta
|
||||||
|
.get_as_kv("from_expr")
|
||||||
|
.map(|s| syn::parse_str::<Expr>(&s).map_err(Error::Syn))
|
||||||
|
.transpose()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let field_name = field.ident.unwrap();
|
||||||
|
|
||||||
|
let col_as = match (from_col, from_expr) {
|
||||||
|
(None, None) => {
|
||||||
|
if entity_ident.is_none() {
|
||||||
|
return Err(Error::EntityNotSpecific);
|
||||||
|
}
|
||||||
|
ColumnAs::Col(format_ident!(
|
||||||
|
"{}",
|
||||||
|
field_name.to_string().to_upper_camel_case()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(None, Some(expr)) => ColumnAs::Expr {
|
||||||
|
expr,
|
||||||
|
field_name: field_name.to_string(),
|
||||||
|
},
|
||||||
|
(Some(col), None) => {
|
||||||
|
if entity_ident.is_none() {
|
||||||
|
return Err(Error::EntityNotSpecific);
|
||||||
|
}
|
||||||
|
|
||||||
|
let field = field_name.to_string();
|
||||||
|
ColumnAs::ColAlias { col, field }
|
||||||
|
}
|
||||||
|
(Some(_), Some(_)) => return Err(Error::BothFromColAndFromExpr(field_span)),
|
||||||
|
};
|
||||||
|
column_as_list.push(col_as);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
entity_ident,
|
||||||
|
ident: input.ident,
|
||||||
|
fields: column_as_list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand(&self) -> syn::Result<TokenStream> {
|
||||||
|
Ok(self.impl_partial_model_trait())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_partial_model_trait(&self) -> TokenStream {
|
||||||
|
let select_ident = format_ident!("select");
|
||||||
|
let DerivePartialModel {
|
||||||
|
entity_ident,
|
||||||
|
ident,
|
||||||
|
fields,
|
||||||
|
} = self;
|
||||||
|
let select_col_code_gen = fields.iter().map(|col_as| match col_as {
|
||||||
|
ColumnAs::Col(ident) => {
|
||||||
|
let entity = entity_ident.as_ref().unwrap();
|
||||||
|
let col_value = quote!( <#entity as sea_orm::EntityTrait>::Column:: #ident);
|
||||||
|
quote!(let #select_ident = sea_orm::SelectColumns::select_column(#select_ident, #col_value);)
|
||||||
|
},
|
||||||
|
ColumnAs::ColAlias { col, field } => {
|
||||||
|
let entity = entity_ident.as_ref().unwrap();
|
||||||
|
let col_value = quote!( <#entity as sea_orm::EntityTrait>::Column:: #col);
|
||||||
|
quote!(let #select_ident = sea_orm::SelectColumns::select_column_as(#select_ident, #col_value, #field);)
|
||||||
|
},
|
||||||
|
ColumnAs::Expr { expr, field_name } => {
|
||||||
|
quote!(let #select_ident = sea_orm::SelectColumns::select_column_as(#select_ident, #expr, #field_name);)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[automatically_derived]
|
||||||
|
impl sea_orm::PartialModelTrait for #ident{
|
||||||
|
fn select_cols<S: sea_orm::SelectColumns>(#select_ident: S) -> S{
|
||||||
|
#(#select_col_code_gen)*
|
||||||
|
#select_ident
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_derive_partial_model(input: syn::DeriveInput) -> syn::Result<TokenStream> {
|
||||||
|
let ident_span = input.ident.span();
|
||||||
|
|
||||||
|
match DerivePartialModel::new(input) {
|
||||||
|
Ok(partial_model) => partial_model.expand(),
|
||||||
|
Err(Error::NotSupportGeneric(span)) => Ok(quote_spanned! {
|
||||||
|
span => compile_error!("you can only derive `DerivePartialModel` on named struct");
|
||||||
|
}),
|
||||||
|
Err(Error::BothFromColAndFromExpr(span)) => Ok(quote_spanned! {
|
||||||
|
span => compile_error!("you can only use one of `from_col` or `from_expr`");
|
||||||
|
}),
|
||||||
|
Err(Error::EntityNotSpecific) => Ok(quote_spanned! {
|
||||||
|
ident_span => compile_error!("you need specific which entity you are using")
|
||||||
|
}),
|
||||||
|
Err(Error::InputNotStruct) => Ok(quote_spanned! {
|
||||||
|
ident_span => compile_error!("you can only derive `DerivePartialModel` on named struct");
|
||||||
|
}),
|
||||||
|
Err(Error::Syn(err)) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod util {
|
||||||
|
use syn::{Lit, Meta, MetaNameValue};
|
||||||
|
|
||||||
|
pub(super) trait GetAsKVMeta {
|
||||||
|
fn get_as_kv(&self, k: &str) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetAsKVMeta for Meta {
|
||||||
|
fn get_as_kv(&self, k: &str) -> Option<String> {
|
||||||
|
let Meta::NameValue(MetaNameValue{path, lit:Lit::Str(lit), ..}) = self else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.is_ident(k) {
|
||||||
|
Some(lit.value())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use quote::format_ident;
|
||||||
|
use syn::DeriveInput;
|
||||||
|
|
||||||
|
use crate::derives::partial_model::ColumnAs;
|
||||||
|
|
||||||
|
use super::DerivePartialModel;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
type StdResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
const CODE_SNIPPET: &str = r#"
|
||||||
|
#[sea_orm(entity = "Entity")]
|
||||||
|
struct PartialModel{
|
||||||
|
default_field: i32,
|
||||||
|
#[sea_orm(from_col = "bar")]
|
||||||
|
alias_field: i32,
|
||||||
|
#[sea_orm(from_expr = "Expr::val(1).add(1)")]
|
||||||
|
expr_field : i32
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
#[test]
|
||||||
|
fn test_load_macro_input() -> StdResult<()> {
|
||||||
|
let input = syn::parse_str::<DeriveInput>(CODE_SNIPPET)?;
|
||||||
|
|
||||||
|
let middle = DerivePartialModel::new(input).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(middle.entity_ident, Some(format_ident!("Entity")));
|
||||||
|
assert_eq!(middle.ident, format_ident!("PartialModel"));
|
||||||
|
assert_eq!(middle.fields.len(), 3);
|
||||||
|
assert_eq!(
|
||||||
|
middle.fields[0],
|
||||||
|
ColumnAs::Col(format_ident!("DefaultField"))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
middle.fields[1],
|
||||||
|
ColumnAs::ColAlias {
|
||||||
|
col: format_ident!("Bar"),
|
||||||
|
field: "alias_field".to_string()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
middle.fields[2],
|
||||||
|
ColumnAs::Expr {
|
||||||
|
expr: syn::parse_str("Expr::val(1).add(1)").unwrap(),
|
||||||
|
field_name: "expr_field".to_string()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
use syn::{parse_macro_input, DeriveInput, Error};
|
use syn::{parse_macro_input, DeriveInput, Error};
|
||||||
|
|
||||||
#[cfg(feature = "derive")]
|
#[cfg(feature = "derive")]
|
||||||
@ -677,6 +678,80 @@ pub fn derive_from_json_query_result(input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The DerivePartialModel derive macro will implement `sea_orm::PartialModelTrait` for simplify partial model queries.
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use sea_orm::{entity::prelude::*, sea_query::Expr, DerivePartialModel, FromQueryResult};
|
||||||
|
/// use serde::{Deserialize, Serialize};
|
||||||
|
///
|
||||||
|
/// #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)]
|
||||||
|
/// #[sea_orm(table_name = "posts")]
|
||||||
|
/// pub struct Model {
|
||||||
|
/// #[sea_orm(primary_key)]
|
||||||
|
/// pub id: i32,
|
||||||
|
/// pub title: String,
|
||||||
|
/// #[sea_orm(column_type = "Text")]
|
||||||
|
/// pub text: String,
|
||||||
|
/// }
|
||||||
|
/// # #[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
|
/// # pub enum Relation {}
|
||||||
|
/// #
|
||||||
|
/// # impl RelationTrait for Relation {
|
||||||
|
/// # fn def(&self) -> RelationDef {
|
||||||
|
/// # panic!("No Relation");
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, FromQueryResult, DerivePartialModel)]
|
||||||
|
/// #[sea_orm(entity = "Entity")]
|
||||||
|
/// struct SelectResult {
|
||||||
|
/// title: String,
|
||||||
|
/// #[sea_orm(from_col = "text")]
|
||||||
|
/// content: String,
|
||||||
|
/// #[sea_orm(from_expr = "Expr::val(1).add(1)")]
|
||||||
|
/// sum: i32,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If all fields in the partial model is `from_expr`, the `entity` can be ignore.
|
||||||
|
/// ```
|
||||||
|
/// use sea_orm::{entity::prelude::*, sea_query::Expr, DerivePartialModel, FromQueryResult};
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, FromQueryResult, DerivePartialModel)]
|
||||||
|
/// struct SelectResult {
|
||||||
|
/// #[sea_orm(from_expr = "Expr::val(1).add(1)")]
|
||||||
|
/// sum: i32,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// A field cannot have attributes `from_col` and `from_expr` at the same time.
|
||||||
|
/// Or, it will result in a compile error.
|
||||||
|
///
|
||||||
|
/// ```compile_fail
|
||||||
|
/// use sea_orm::{entity::prelude::*, FromQueryResult, DerivePartialModel, sea_query::Expr};
|
||||||
|
///
|
||||||
|
/// #[derive(Debug, FromQueryResult, DerivePartialModel)]
|
||||||
|
/// #[sea_orm(entity = "Entity")]
|
||||||
|
/// struct SelectResult {
|
||||||
|
/// #[sea_orm(from_expr = "Expr::val(1).add(1)", from_col = "foo")]
|
||||||
|
/// sum: i32
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "derive")]
|
||||||
|
#[proc_macro_derive(DerivePartialModel, attributes(sea_orm))]
|
||||||
|
pub fn derive_partial_model(input: TokenStream) -> TokenStream {
|
||||||
|
let derive_input = parse_macro_input!(input);
|
||||||
|
|
||||||
|
match derives::expand_derive_partial_model(derive_input) {
|
||||||
|
Ok(token_stream) => token_stream.into(),
|
||||||
|
Err(e) => e.to_compile_error().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[cfg(feature = "derive")]
|
#[cfg(feature = "derive")]
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
|
@ -103,6 +103,7 @@ mod column;
|
|||||||
mod identity;
|
mod identity;
|
||||||
mod link;
|
mod link;
|
||||||
mod model;
|
mod model;
|
||||||
|
mod partial_model;
|
||||||
/// Re-export common types from the entity
|
/// Re-export common types from the entity
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
mod primary_key;
|
mod primary_key;
|
||||||
@ -115,6 +116,7 @@ pub use column::*;
|
|||||||
pub use identity::*;
|
pub use identity::*;
|
||||||
pub use link::*;
|
pub use link::*;
|
||||||
pub use model::*;
|
pub use model::*;
|
||||||
|
pub use partial_model::*;
|
||||||
// pub use prelude::*;
|
// pub use prelude::*;
|
||||||
pub use primary_key::*;
|
pub use primary_key::*;
|
||||||
pub use relation::*;
|
pub use relation::*;
|
||||||
|
7
src/entity/partial_model.rs
Normal file
7
src/entity/partial_model.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use crate::{FromQueryResult, SelectColumns};
|
||||||
|
|
||||||
|
/// A trait for a part of [Model](super::model::ModelTrait)
|
||||||
|
pub trait PartialModelTrait: FromQueryResult {
|
||||||
|
/// Select specific columns this [PartialModel] needs
|
||||||
|
fn select_cols<S: SelectColumns>(select: S) -> S;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Identity, IntoIdentity, QueryOrder,
|
ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Identity, IntoIdentity,
|
||||||
Select, SelectModel, SelectorTrait,
|
PartialModelTrait, QueryOrder, QuerySelect, Select, SelectModel, SelectorTrait,
|
||||||
};
|
};
|
||||||
use sea_query::{
|
use sea_query::{
|
||||||
Condition, DynIden, Expr, IntoValueTuple, Order, SeaRc, SelectStatement, SimpleExpr, Value,
|
Condition, DynIden, Expr, IntoValueTuple, Order, SeaRc, SelectStatement, SimpleExpr, Value,
|
||||||
@ -234,6 +234,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a [Selector] from `Self` that wraps a [SelectModel] with a [PartialModel](PartialModelTrait)
|
||||||
|
pub fn into_partial_model<M>(self) -> Cursor<SelectModel<M>>
|
||||||
|
where
|
||||||
|
M: PartialModelTrait,
|
||||||
|
{
|
||||||
|
M::select_cols(QuerySelect::select_only(self)).into_model::<M>()
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a [Cursor] that fetch JSON value
|
/// Construct a [Cursor] that fetch JSON value
|
||||||
#[cfg(feature = "with-json")]
|
#[cfg(feature = "with-json")]
|
||||||
pub fn into_json(self) -> Cursor<SelectModel<JsonValue>> {
|
pub fn into_json(self) -> Cursor<SelectModel<JsonValue>> {
|
||||||
@ -247,6 +255,17 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> QuerySelect for Cursor<S>
|
||||||
|
where
|
||||||
|
S: SelectorTrait,
|
||||||
|
{
|
||||||
|
type QueryStatement = SelectStatement;
|
||||||
|
|
||||||
|
fn query(&mut self) -> &mut SelectStatement {
|
||||||
|
&mut self.query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S> QueryOrder for Cursor<S>
|
impl<S> QueryOrder for Cursor<S>
|
||||||
where
|
where
|
||||||
S: SelectorTrait,
|
S: SelectorTrait,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::*, ConnectionTrait, EntityTrait, FromQueryResult, IdenStatic, Iterable, ModelTrait,
|
error::*, ConnectionTrait, EntityTrait, FromQueryResult, IdenStatic, Iterable, ModelTrait,
|
||||||
PrimaryKeyToColumn, QueryResult, Select, SelectA, SelectB, SelectTwo, SelectTwoMany, Statement,
|
PartialModelTrait, PrimaryKeyToColumn, QueryResult, QuerySelect, Select, SelectA, SelectB,
|
||||||
StreamTrait, TryGetableMany,
|
SelectTwo, SelectTwoMany, Statement, StreamTrait, TryGetableMany,
|
||||||
};
|
};
|
||||||
use futures::{Stream, TryStreamExt};
|
use futures::{Stream, TryStreamExt};
|
||||||
use sea_query::SelectStatement;
|
use sea_query::SelectStatement;
|
||||||
@ -154,6 +154,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a [Selector] from `Self` that wraps a [SelectModel] with a [PartialModel](PartialModelTrait)
|
||||||
|
pub fn into_partial_model<M>(self) -> Selector<SelectModel<M>>
|
||||||
|
where
|
||||||
|
M: PartialModelTrait,
|
||||||
|
{
|
||||||
|
M::select_cols(QuerySelect::select_only(self)).into_model::<M>()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a selectable Model as a [JsonValue] for SQL JSON operations
|
/// Get a selectable Model as a [JsonValue] for SQL JSON operations
|
||||||
#[cfg(feature = "with-json")]
|
#[cfg(feature = "with-json")]
|
||||||
pub fn into_json(self) -> Selector<SelectModel<JsonValue>> {
|
pub fn into_json(self) -> Selector<SelectModel<JsonValue>> {
|
||||||
@ -397,6 +405,18 @@ where
|
|||||||
{
|
{
|
||||||
self.into_model().stream(db).await
|
self.into_model().stream(db).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stream the result of the operation with PartialModel
|
||||||
|
pub async fn stream_partial_model<'a: 'b, 'b, C, M>(
|
||||||
|
self,
|
||||||
|
db: &'a C,
|
||||||
|
) -> Result<impl Stream<Item = Result<M, DbErr>> + 'b + Send, DbErr>
|
||||||
|
where
|
||||||
|
C: ConnectionTrait + StreamTrait + Send,
|
||||||
|
M: PartialModelTrait + Send + 'b,
|
||||||
|
{
|
||||||
|
self.into_partial_model().stream(db).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E, F> SelectTwo<E, F>
|
impl<E, F> SelectTwo<E, F>
|
||||||
@ -416,6 +436,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform a conversion into a [SelectTwoModel] with [PartialModel](PartialModelTrait)
|
||||||
|
pub fn into_partial_model<M, N>(self) -> Selector<SelectTwoModel<M, N>>
|
||||||
|
where
|
||||||
|
M: PartialModelTrait,
|
||||||
|
N: PartialModelTrait,
|
||||||
|
{
|
||||||
|
let select = QuerySelect::select_only(self);
|
||||||
|
let select = M::select_cols(select);
|
||||||
|
let select = N::select_cols(select);
|
||||||
|
select.into_model::<M, N>()
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert the Models into JsonValue
|
/// Convert the Models into JsonValue
|
||||||
#[cfg(feature = "with-json")]
|
#[cfg(feature = "with-json")]
|
||||||
pub fn into_json(self) -> Selector<SelectTwoModel<JsonValue, JsonValue>> {
|
pub fn into_json(self) -> Selector<SelectTwoModel<JsonValue, JsonValue>> {
|
||||||
@ -451,6 +483,19 @@ where
|
|||||||
{
|
{
|
||||||
self.into_model().stream(db).await
|
self.into_model().stream(db).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stream the result of the operation with PartialModel
|
||||||
|
pub async fn stream_partial_model<'a: 'b, 'b, C, M, N>(
|
||||||
|
self,
|
||||||
|
db: &'a C,
|
||||||
|
) -> Result<impl Stream<Item = Result<(M, Option<N>), DbErr>> + 'b + Send, DbErr>
|
||||||
|
where
|
||||||
|
C: ConnectionTrait + StreamTrait + Send,
|
||||||
|
M: PartialModelTrait + Send + 'b,
|
||||||
|
N: PartialModelTrait + Send + 'b,
|
||||||
|
{
|
||||||
|
self.into_partial_model().stream(db).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E, F> SelectTwoMany<E, F>
|
impl<E, F> SelectTwoMany<E, F>
|
||||||
@ -470,6 +515,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs a conversion to [Selector] with partial model
|
||||||
|
fn into_partial_model<M, N>(self) -> Selector<SelectTwoModel<M, N>>
|
||||||
|
where
|
||||||
|
M: PartialModelTrait,
|
||||||
|
N: PartialModelTrait,
|
||||||
|
{
|
||||||
|
let select = self.select_only();
|
||||||
|
let select = M::select_cols(select);
|
||||||
|
let select = N::select_cols(select);
|
||||||
|
select.into_model()
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert the results to JSON
|
/// Convert the results to JSON
|
||||||
#[cfg(feature = "with-json")]
|
#[cfg(feature = "with-json")]
|
||||||
pub fn into_json(self) -> Selector<SelectTwoModel<JsonValue, JsonValue>> {
|
pub fn into_json(self) -> Selector<SelectTwoModel<JsonValue, JsonValue>> {
|
||||||
@ -490,6 +547,19 @@ where
|
|||||||
self.into_model().stream(db).await
|
self.into_model().stream(db).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stream the result of the operation with PartialModel
|
||||||
|
pub async fn stream_partial_model<'a: 'b, 'b, C, M, N>(
|
||||||
|
self,
|
||||||
|
db: &'a C,
|
||||||
|
) -> Result<impl Stream<Item = Result<(M, Option<N>), DbErr>> + 'b + Send, DbErr>
|
||||||
|
where
|
||||||
|
C: ConnectionTrait + StreamTrait + Send,
|
||||||
|
M: PartialModelTrait + Send + 'b,
|
||||||
|
N: PartialModelTrait + Send + 'b,
|
||||||
|
{
|
||||||
|
self.into_partial_model().stream(db).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Get all Models from the select operation
|
/// Get all Models from the select operation
|
||||||
///
|
///
|
||||||
/// > `SelectTwoMany::one()` method has been dropped (#486)
|
/// > `SelectTwoMany::one()` method has been dropped (#486)
|
||||||
|
@ -350,8 +350,8 @@ pub use schema::*;
|
|||||||
pub use sea_orm_macros::{
|
pub use sea_orm_macros::{
|
||||||
DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn,
|
DeriveActiveEnum, DeriveActiveModel, DeriveActiveModelBehavior, DeriveColumn,
|
||||||
DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel,
|
DeriveCustomColumn, DeriveEntity, DeriveEntityModel, DeriveIntoActiveModel,
|
||||||
DeriveMigrationName, DeriveModel, DerivePrimaryKey, DeriveRelation, FromJsonQueryResult,
|
DeriveMigrationName, DeriveModel, DerivePartialModel, DerivePrimaryKey, DeriveRelation,
|
||||||
FromQueryResult,
|
FromJsonQueryResult, FromQueryResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use sea_query;
|
pub use sea_query;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{DbBackend, Statement};
|
use crate::{ColumnTrait, DbBackend, IntoIdentity, IntoSimpleExpr, QuerySelect, Statement};
|
||||||
use sea_query::QueryStatementBuilder;
|
use sea_query::QueryStatementBuilder;
|
||||||
|
|
||||||
/// A Trait for any type performing queries on a Model or ActiveModel
|
/// A Trait for any type performing queries on a Model or ActiveModel
|
||||||
@ -55,3 +55,36 @@ pub trait QueryTrait {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Select specific column for partial model queries
|
||||||
|
pub trait SelectColumns {
|
||||||
|
/// Add a select column
|
||||||
|
///
|
||||||
|
/// For more detail, please visit [QuerySelect::column]
|
||||||
|
fn select_column<C: ColumnTrait>(self, col: C) -> Self;
|
||||||
|
|
||||||
|
/// Add a select column with alias
|
||||||
|
///
|
||||||
|
/// For more detail, please visit [QuerySelect::column_as]
|
||||||
|
fn select_column_as<C, I>(self, col: C, alias: I) -> Self
|
||||||
|
where
|
||||||
|
C: IntoSimpleExpr,
|
||||||
|
I: IntoIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> SelectColumns for S
|
||||||
|
where
|
||||||
|
S: QuerySelect,
|
||||||
|
{
|
||||||
|
fn select_column<C: ColumnTrait>(self, col: C) -> Self {
|
||||||
|
QuerySelect::column(self, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_column_as<C, I>(self, col: C, alias: I) -> Self
|
||||||
|
where
|
||||||
|
C: IntoSimpleExpr,
|
||||||
|
I: IntoIdentity,
|
||||||
|
{
|
||||||
|
QuerySelect::column_as(self, col, alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,9 @@ pub mod common;
|
|||||||
|
|
||||||
pub use common::{features::*, setup::*, TestContext};
|
pub use common::{features::*, setup::*, TestContext};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection};
|
use sea_orm::{
|
||||||
|
entity::prelude::*, entity::*, DatabaseConnection, DerivePartialModel, FromQueryResult,
|
||||||
|
};
|
||||||
|
|
||||||
#[sea_orm_macros::test]
|
#[sea_orm_macros::test]
|
||||||
#[cfg(all(feature = "sqlx-postgres", feature = "postgres-array"))]
|
#[cfg(all(feature = "sqlx-postgres", feature = "postgres-array"))]
|
||||||
@ -11,6 +13,7 @@ async fn main() -> Result<(), DbErr> {
|
|||||||
create_tables(&ctx.db).await?;
|
create_tables(&ctx.db).await?;
|
||||||
insert_collection(&ctx.db).await?;
|
insert_collection(&ctx.db).await?;
|
||||||
update_collection(&ctx.db).await?;
|
update_collection(&ctx.db).await?;
|
||||||
|
select_collection(&ctx.db).await?;
|
||||||
ctx.delete().await;
|
ctx.delete().await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -149,3 +152,27 @@ pub async fn update_collection(db: &DatabaseConnection) -> Result<(), DbErr> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn select_collection(db: &DatabaseConnection) -> Result<(), DbErr> {
|
||||||
|
use collection::*;
|
||||||
|
|
||||||
|
#[derive(DerivePartialModel, FromQueryResult, Debug, PartialEq)]
|
||||||
|
#[sea_orm(entity = "Entity")]
|
||||||
|
struct PartialSelectResult {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = Entity::find_by_id(1)
|
||||||
|
.into_partial_model::<PartialSelectResult>()
|
||||||
|
.one(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Some(PartialSelectResult {
|
||||||
|
name: "Collection 1".into(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ pub mod common;
|
|||||||
|
|
||||||
pub use common::{features::*, setup::*, TestContext};
|
pub use common::{features::*, setup::*, TestContext};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use sea_orm::{entity::prelude::*, FromQueryResult};
|
use sea_orm::{entity::prelude::*, DerivePartialModel, FromQueryResult};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
#[sea_orm_macros::test]
|
#[sea_orm_macros::test]
|
||||||
@ -202,7 +202,7 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
|
|||||||
|
|
||||||
// Fetch custom struct
|
// Fetch custom struct
|
||||||
|
|
||||||
#[derive(FromQueryResult, Debug, PartialEq)]
|
#[derive(FromQueryResult, Debug, PartialEq, Clone)]
|
||||||
struct Row {
|
struct Row {
|
||||||
id: i32,
|
id: i32,
|
||||||
}
|
}
|
||||||
@ -233,5 +233,44 @@ pub async fn cursor_pagination(db: &DatabaseConnection) -> Result<(), DbErr> {
|
|||||||
[json!({ "id": 6 }), json!({ "id": 7 })]
|
[json!({ "id": 6 }), json!({ "id": 7 })]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[derive(DerivePartialModel, FromQueryResult, Debug, PartialEq, Clone)]
|
||||||
|
#[sea_orm(entity = "Entity")]
|
||||||
|
struct PartialRow {
|
||||||
|
#[sea_orm(from_col = "id")]
|
||||||
|
id: i32,
|
||||||
|
#[sea_orm(from_expr = "sea_query::Expr::col(Column::Id).add(1000)")]
|
||||||
|
id_shifted: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = cursor.into_partial_model::<PartialRow>();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
cursor.first(2).all(db).await?,
|
||||||
|
[
|
||||||
|
PartialRow {
|
||||||
|
id: 6,
|
||||||
|
id_shifted: 1006,
|
||||||
|
},
|
||||||
|
PartialRow {
|
||||||
|
id: 7,
|
||||||
|
id_shifted: 1007,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
cursor.first(3).all(db).await?,
|
||||||
|
[
|
||||||
|
PartialRow {
|
||||||
|
id: 6,
|
||||||
|
id_shifted: 1006,
|
||||||
|
},
|
||||||
|
PartialRow {
|
||||||
|
id: 7,
|
||||||
|
id_shifted: 1007,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
47
tests/partial_model_tests.rs
Normal file
47
tests/partial_model_tests.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
use entity::{Column, Entity};
|
||||||
|
use sea_orm::{ColumnTrait, DerivePartialModel, FromQueryResult};
|
||||||
|
use sea_query::Expr;
|
||||||
|
|
||||||
|
mod entity {
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "foo_table")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
id: i32,
|
||||||
|
foo: i32,
|
||||||
|
bar: String,
|
||||||
|
foo2: bool,
|
||||||
|
bar2: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, DeriveRelation, EnumIter)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromQueryResult, DerivePartialModel)]
|
||||||
|
#[sea_orm(entity = "Entity")]
|
||||||
|
struct SimpleTest {
|
||||||
|
_foo: i32,
|
||||||
|
_bar: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromQueryResult, DerivePartialModel)]
|
||||||
|
#[sea_orm(entity = "Entity")]
|
||||||
|
struct FieldFromDiffNameColumnTest {
|
||||||
|
#[sea_orm(from_col = "foo2")]
|
||||||
|
_foo: i32,
|
||||||
|
#[sea_orm(from_col = "bar2")]
|
||||||
|
_bar: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromQueryResult, DerivePartialModel)]
|
||||||
|
struct FieldFromExpr {
|
||||||
|
#[sea_orm(from_expr = "Column::Bar2.sum()")]
|
||||||
|
_foo: f64,
|
||||||
|
#[sea_orm(from_expr = "Expr::col(Column::Id).equals(Column::Foo)")]
|
||||||
|
_bar: bool,
|
||||||
|
}
|
@ -4,7 +4,8 @@ pub use chrono::offset::Utc;
|
|||||||
pub use common::{bakery_chain::*, setup::*, TestContext};
|
pub use common::{bakery_chain::*, setup::*, TestContext};
|
||||||
pub use rust_decimal::prelude::*;
|
pub use rust_decimal::prelude::*;
|
||||||
pub use rust_decimal_macros::dec;
|
pub use rust_decimal_macros::dec;
|
||||||
pub use sea_orm::{entity::*, query::*, DbErr, FromQueryResult};
|
pub use sea_orm::{entity::*, query::*, DbErr, DerivePartialModel, FromQueryResult};
|
||||||
|
pub use sea_query::{Alias, Expr, Func, SimpleExpr};
|
||||||
pub use uuid::Uuid;
|
pub use uuid::Uuid;
|
||||||
|
|
||||||
// Run the test locally:
|
// Run the test locally:
|
||||||
@ -66,6 +67,7 @@ pub async fn left_join() {
|
|||||||
.filter(baker::Column::Name.contains("Baker 1"));
|
.filter(baker::Column::Name.contains("Baker 1"));
|
||||||
|
|
||||||
let result = select
|
let result = select
|
||||||
|
.clone()
|
||||||
.into_model::<SelectResult>()
|
.into_model::<SelectResult>()
|
||||||
.one(&ctx.db)
|
.one(&ctx.db)
|
||||||
.await
|
.await
|
||||||
@ -74,6 +76,28 @@ pub async fn left_join() {
|
|||||||
assert_eq!(result.name.as_str(), "Baker 1");
|
assert_eq!(result.name.as_str(), "Baker 1");
|
||||||
assert_eq!(result.bakery_name, Some("SeaSide Bakery".to_string()));
|
assert_eq!(result.bakery_name, Some("SeaSide Bakery".to_string()));
|
||||||
|
|
||||||
|
#[derive(DerivePartialModel, FromQueryResult, Debug, PartialEq)]
|
||||||
|
#[sea_orm(entity = "Baker")]
|
||||||
|
struct PartialSelectResult {
|
||||||
|
name: String,
|
||||||
|
#[sea_orm(from_expr = "Expr::col((bakery::Entity, bakery::Column::Name))")]
|
||||||
|
bakery_name: Option<String>,
|
||||||
|
#[sea_orm(
|
||||||
|
from_expr = r#"SimpleExpr::FunctionCall(Func::upper(Expr::col((bakery::Entity, bakery::Column::Name))))"#
|
||||||
|
)]
|
||||||
|
bakery_name_upper: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = select
|
||||||
|
.into_partial_model::<PartialSelectResult>()
|
||||||
|
.one(&ctx.db)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(result.name.as_str(), "Baker 1");
|
||||||
|
assert_eq!(result.bakery_name, Some("SeaSide Bakery".to_string()));
|
||||||
|
assert_eq!(result.bakery_name_upper, Some("SEASIDE BAKERY".to_string()));
|
||||||
|
|
||||||
let select = baker::Entity::find()
|
let select = baker::Entity::find()
|
||||||
.left_join(bakery::Entity)
|
.left_join(bakery::Entity)
|
||||||
.select_only()
|
.select_only()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user