diff --git a/sea-orm-macros/src/derives/attributes.rs b/sea-orm-macros/src/derives/attributes.rs index a5cab499af..12d6b837ad 100644 --- a/sea-orm-macros/src/derives/attributes.rs +++ b/sea-orm-macros/src/derives/attributes.rs @@ -77,6 +77,7 @@ pub mod value_type_attr { pub from_str: Option, pub to_str: Option, pub try_from_u64: Option<()>, + pub no_vec_impl: Option<()>, } } diff --git a/sea-orm-macros/src/derives/value_type.rs b/sea-orm-macros/src/derives/value_type.rs index 77c8630535..b3d2bf37e2 100644 --- a/sea-orm-macros/src/derives/value_type.rs +++ b/sea-orm-macros/src/derives/value_type.rs @@ -1,3 +1,5 @@ +use crate::derives::value_type_match::omit_vec_impl; + use super::attributes::value_type_attr; use super::value_type_match::{array_type_expr, can_try_from_u64, column_type_expr}; use proc_macro2::TokenStream; @@ -15,6 +17,8 @@ struct DeriveValueTypeStruct { ty: Type, column_type: TokenStream, array_type: TokenStream, + /// Do not implement `sea_orm::TryGetableArray` for this type. Default: false. + no_vec_impl: bool, can_try_from_u64: bool, } @@ -23,6 +27,7 @@ struct DeriveValueTypeStructAttrs { column_type: Option, array_type: Option, try_from_u64: bool, + no_vec_impl: bool, } impl TryFrom for DeriveValueTypeStructAttrs { @@ -33,6 +38,7 @@ impl TryFrom for DeriveValueTypeStructAttrs { column_type: attrs.column_type.map(|s| s.parse()).transpose()?, array_type: attrs.array_type.map(|s| s.parse()).transpose()?, try_from_u64: attrs.try_from_u64.is_some(), + no_vec_impl: attrs.no_vec_impl.is_some(), }) } } @@ -151,12 +157,14 @@ impl DeriveValueTypeStruct { let column_type = column_type_expr(attrs.column_type, field_type, field_span); let array_type = array_type_expr(attrs.array_type, field_type, field_span); let can_try_from_u64 = attrs.try_from_u64 || can_try_from_u64(field_type); + let no_vec_impl = attrs.no_vec_impl || omit_vec_impl(field_type); Ok(Self { name, ty, column_type, array_type, + no_vec_impl, can_try_from_u64, }) } @@ -185,6 +193,25 @@ impl DeriveValueTypeStruct { quote!() }; + let impl_try_getable_array = if cfg!(feature = "postgres-array") && !self.no_vec_impl { + quote!( + #[automatically_derived] + impl sea_orm::TryGetableArray for #name { + fn try_get_by( + res: &sea_orm::QueryResult, + index: I, + ) -> std::result::Result, sea_orm::TryGetError> { + Ok( as sea_orm::TryGetable>::try_get_by(res, index)? + .into_iter() + .map(|value| Self(value)) + .collect()) + } + } + ) + } else { + quote!() + }; + let impl_not_u8 = if cfg!(feature = "postgres-array") { quote!( #[automatically_derived] @@ -198,6 +225,7 @@ impl DeriveValueTypeStruct { #[automatically_derived] impl std::convert::From<#name> for sea_orm::Value { fn from(source: #name) -> Self { + println!("Struct"); source.0.into() } } @@ -243,6 +271,8 @@ impl DeriveValueTypeStruct { } } + #impl_try_getable_array + #try_from_u64_impl #impl_not_u8 @@ -275,6 +305,36 @@ impl DeriveValueTypeString { None => "e!(String(sea_orm::sea_query::StringLen::None)), }; + let impl_try_getable_array = if cfg!(feature = "postgres-array") { + quote!( + #[automatically_derived] + impl sea_orm::TryGetableArray for #name { + fn try_get_by( + res: &sea_orm::QueryResult, + index: I, + ) -> std::result::Result, sea_orm::TryGetError> { + let mut result = Vec::new(); + for string in as sea_orm::TryGetable>::try_get_by(res, index)?.into_iter() { + result.push(#from_str(&string) + .map_err(|err| + { + sea_orm::TryGetError::DbErr( + sea_orm::DbErr::TryIntoErr { + from: "String", + into: stringify!(#name), + source: std::sync::Arc::new(err), + }) + } + )?); + } + Ok(result) + } + } + ) + } else { + quote!() + }; + let impl_not_u8 = if cfg!(feature = "postgres-array") { quote!( #[automatically_derived] @@ -342,6 +402,8 @@ impl DeriveValueTypeString { } #impl_not_u8 + + #impl_try_getable_array ) } } diff --git a/sea-orm-macros/src/derives/value_type_match.rs b/sea-orm-macros/src/derives/value_type_match.rs index cfba21d052..26360838f5 100644 --- a/sea-orm-macros/src/derives/value_type_match.rs +++ b/sea-orm-macros/src/derives/value_type_match.rs @@ -151,6 +151,29 @@ pub fn can_try_from_u64(field_type: &str) -> bool { ) } +/// Maximum depth of vector nesting allowed INSIDE NEW TYPE before omitting sea_orm::TryGetableArray +/// For example, `struct A (Vec>)` has dimensionality of 2 +/// Abosolute maximum would be 5, because of Postgres limit of 6 +const MAX_VEC_DIMENSIONALITY: u8 = 0; + +/// Determines whether to omit `sea_orm::TryGetableArray` implementation for a given field type +/// based on the vector dimensionality. +pub fn omit_vec_impl(field_type: &str) -> bool { + let mut depth = 0u8; + let mut current = field_type.trim(); + + while let Some(inner) = current.strip_prefix("Vec<") { + #[allow(clippy::absurd_extreme_comparisons)] + if depth >= MAX_VEC_DIMENSIONALITY { + return true; + } + depth += 1; + current = inner.trim_start(); + } + + false +} + /// Return whether it is nullable fn trim_option(s: &str) -> (bool, &str) { if s.starts_with("Option<") { diff --git a/sea-orm-sync/tests/common/features/value_type.rs b/sea-orm-sync/tests/common/features/value_type.rs index a58dbc283b..a963f2c522 100644 --- a/sea-orm-sync/tests/common/features/value_type.rs +++ b/sea-orm-sync/tests/common/features/value_type.rs @@ -69,9 +69,15 @@ where } } +// Automatically disable vec impl #[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] pub struct StringVec(pub Vec); +// Explicitly disable vec impl +#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] +#[sea_orm(no_vec_impl)] +pub struct StringVecNoImpl(pub Vec); + #[derive(Copy, Clone, Debug, PartialEq, Eq, DeriveValueType)] #[sea_orm(value_type = "String")] pub enum Tag1 { diff --git a/sea-orm-sync/tests/derive_tests.rs b/sea-orm-sync/tests/derive_tests.rs index e482280052..e33cae65c7 100644 --- a/sea-orm-sync/tests/derive_tests.rs +++ b/sea-orm-sync/tests/derive_tests.rs @@ -69,3 +69,69 @@ struct FromQueryResultNested { #[sea_orm(nested)] _test: SimpleTest, } + +#[cfg(feature = "postgres-array")] +mod postgres_array { + use crate::FromQueryResult; + use sea_orm::DeriveValueType; + + #[derive(DeriveValueType)] + pub struct IngredientId(i32); + + #[derive(Copy, Clone, Debug, PartialEq, Eq, DeriveValueType)] + #[sea_orm(value_type = "String")] + pub struct NumericLabel { + pub value: i64, + } + + impl std::fmt::Display for NumericLabel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.value) + } + } + + impl std::str::FromStr for NumericLabel { + type Err = std::num::ParseIntError; + fn from_str(s: &str) -> Result { + Ok(Self { value: s.parse()? }) + } + } + + #[derive(Copy, Clone, Debug, PartialEq, Eq, DeriveValueType)] + #[sea_orm(value_type = "String")] + pub enum TextureKind { + Hard, + Soft, + } + + impl std::fmt::Display for TextureKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Hard => "hard", + Self::Soft => "soft", + } + ) + } + } + + impl std::str::FromStr for TextureKind { + type Err = sea_query::ValueTypeErr; + fn from_str(s: &str) -> Result { + Ok(match s { + "hard" => Self::Hard, + "soft" => Self::Soft, + _ => return Err(sea_query::ValueTypeErr), + }) + } + } + + #[derive(FromQueryResult)] + pub struct IngredientPathRow { + pub ingredient_path: Vec, + pub numeric_label_path: Vec, + pub texture_path: Vec, + } +} diff --git a/tests/common/features/value_type.rs b/tests/common/features/value_type.rs index a58dbc283b..a963f2c522 100644 --- a/tests/common/features/value_type.rs +++ b/tests/common/features/value_type.rs @@ -69,9 +69,15 @@ where } } +// Automatically disable vec impl #[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] pub struct StringVec(pub Vec); +// Explicitly disable vec impl +#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)] +#[sea_orm(no_vec_impl)] +pub struct StringVecNoImpl(pub Vec); + #[derive(Copy, Clone, Debug, PartialEq, Eq, DeriveValueType)] #[sea_orm(value_type = "String")] pub enum Tag1 { diff --git a/tests/derive_tests.rs b/tests/derive_tests.rs index e482280052..e33cae65c7 100644 --- a/tests/derive_tests.rs +++ b/tests/derive_tests.rs @@ -69,3 +69,69 @@ struct FromQueryResultNested { #[sea_orm(nested)] _test: SimpleTest, } + +#[cfg(feature = "postgres-array")] +mod postgres_array { + use crate::FromQueryResult; + use sea_orm::DeriveValueType; + + #[derive(DeriveValueType)] + pub struct IngredientId(i32); + + #[derive(Copy, Clone, Debug, PartialEq, Eq, DeriveValueType)] + #[sea_orm(value_type = "String")] + pub struct NumericLabel { + pub value: i64, + } + + impl std::fmt::Display for NumericLabel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.value) + } + } + + impl std::str::FromStr for NumericLabel { + type Err = std::num::ParseIntError; + fn from_str(s: &str) -> Result { + Ok(Self { value: s.parse()? }) + } + } + + #[derive(Copy, Clone, Debug, PartialEq, Eq, DeriveValueType)] + #[sea_orm(value_type = "String")] + pub enum TextureKind { + Hard, + Soft, + } + + impl std::fmt::Display for TextureKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Hard => "hard", + Self::Soft => "soft", + } + ) + } + } + + impl std::str::FromStr for TextureKind { + type Err = sea_query::ValueTypeErr; + fn from_str(s: &str) -> Result { + Ok(match s { + "hard" => Self::Hard, + "soft" => Self::Soft, + _ => return Err(sea_query::ValueTypeErr), + }) + } + } + + #[derive(FromQueryResult)] + pub struct IngredientPathRow { + pub ingredient_path: Vec, + pub numeric_label_path: Vec, + pub texture_path: Vec, + } +}