From 998971c94eca20a8a124aaf8c21e7b1684678efa Mon Sep 17 00:00:00 2001 From: sinder Date: Fri, 27 Feb 2026 19:29:27 +0800 Subject: [PATCH 1/4] Add fix and tests --- sea-orm-macros/src/derives/value_type.rs | 22 ++++++++++++++++++++++ sea-orm-sync/tests/derive_tests.rs | 14 ++++++++++++++ tests/derive_tests.rs | 14 ++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/sea-orm-macros/src/derives/value_type.rs b/sea-orm-macros/src/derives/value_type.rs index 77c8630535..016f5dcbb4 100644 --- a/sea-orm-macros/src/derives/value_type.rs +++ b/sea-orm-macros/src/derives/value_type.rs @@ -185,6 +185,25 @@ impl DeriveValueTypeStruct { quote!() }; + 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> { + 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 +217,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 +263,8 @@ impl DeriveValueTypeStruct { } } + #impl_try_getable_array + #try_from_u64_impl #impl_not_u8 diff --git a/sea-orm-sync/tests/derive_tests.rs b/sea-orm-sync/tests/derive_tests.rs index e482280052..d972130ead 100644 --- a/sea-orm-sync/tests/derive_tests.rs +++ b/sea-orm-sync/tests/derive_tests.rs @@ -69,3 +69,17 @@ 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 GoodId(i32); + + #[derive(FromQueryResult)] + pub struct ArrayTest { + pub ingredient_path: Vec, + } +} diff --git a/tests/derive_tests.rs b/tests/derive_tests.rs index e482280052..3d392f47ec 100644 --- a/tests/derive_tests.rs +++ b/tests/derive_tests.rs @@ -69,3 +69,17 @@ 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 GoodId(i32); + + #[derive(FromQueryResult)] + pub struct ArrayTest { + pub ingredient_path: Vec, + } +} From 9f85b5ddca2351c9ff255bbbdd4a7b1ac0759f6b Mon Sep 17 00:00:00 2001 From: sinder Date: Tue, 10 Mar 2026 17:03:33 +0800 Subject: [PATCH 2/4] Fix NewType with vec bug Add a new derive attribute `no_vec_impl` to explicitly omit `sea_orm::TryGetableArray` --- sea-orm-macros/src/derives/attributes.rs | 1 + sea-orm-macros/src/derives/value_type.rs | 10 ++++++++- .../src/derives/value_type_match.rs | 22 +++++++++++++++++++ tests/common/features/value_type.rs | 6 +++++ 4 files changed, 38 insertions(+), 1 deletion(-) 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 016f5dcbb4..c7171f1479 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,7 +193,7 @@ impl DeriveValueTypeStruct { quote!() }; - let impl_try_getable_array = if cfg!(feature = "postgres-array") { + let impl_try_getable_array = if cfg!(feature = "postgres-array") && !self.no_vec_impl { quote!( #[automatically_derived] impl sea_orm::TryGetableArray for #name { diff --git a/sea-orm-macros/src/derives/value_type_match.rs b/sea-orm-macros/src/derives/value_type_match.rs index cfba21d052..d1d0be1781 100644 --- a/sea-orm-macros/src/derives/value_type_match.rs +++ b/sea-orm-macros/src/derives/value_type_match.rs @@ -151,6 +151,28 @@ 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<") { + 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/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 { From 9b465975b23ae37c8dc064fea904d480d92e3384 Mon Sep 17 00:00:00 2001 From: sinder Date: Tue, 10 Mar 2026 18:01:23 +0800 Subject: [PATCH 3/4] Fix array implementation for DerivValueTypeString --- sea-orm-macros/src/derives/value_type.rs | 32 +++++++++++++ tests/derive_tests.rs | 58 ++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/sea-orm-macros/src/derives/value_type.rs b/sea-orm-macros/src/derives/value_type.rs index c7171f1479..b3d2bf37e2 100644 --- a/sea-orm-macros/src/derives/value_type.rs +++ b/sea-orm-macros/src/derives/value_type.rs @@ -305,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] @@ -372,6 +402,8 @@ impl DeriveValueTypeString { } #impl_not_u8 + + #impl_try_getable_array ) } } diff --git a/tests/derive_tests.rs b/tests/derive_tests.rs index 3d392f47ec..e33cae65c7 100644 --- a/tests/derive_tests.rs +++ b/tests/derive_tests.rs @@ -76,10 +76,62 @@ mod postgres_array { use sea_orm::DeriveValueType; #[derive(DeriveValueType)] - pub struct GoodId(i32); + 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 ArrayTest { - pub ingredient_path: Vec, + pub struct IngredientPathRow { + pub ingredient_path: Vec, + pub numeric_label_path: Vec, + pub texture_path: Vec, } } From 5e7ee973a1472b5fd67dc99e22f415b33ca305d6 Mon Sep 17 00:00:00 2001 From: sinder Date: Tue, 10 Mar 2026 18:22:47 +0800 Subject: [PATCH 4/4] add tests to sea-orm-sync allow absurd_extreme_comparisons --- .../src/derives/value_type_match.rs | 1 + .../tests/common/features/value_type.rs | 6 ++ sea-orm-sync/tests/derive_tests.rs | 58 ++++++++++++++++++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/sea-orm-macros/src/derives/value_type_match.rs b/sea-orm-macros/src/derives/value_type_match.rs index d1d0be1781..26360838f5 100644 --- a/sea-orm-macros/src/derives/value_type_match.rs +++ b/sea-orm-macros/src/derives/value_type_match.rs @@ -163,6 +163,7 @@ pub fn omit_vec_impl(field_type: &str) -> bool { 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; } 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 d972130ead..e33cae65c7 100644 --- a/sea-orm-sync/tests/derive_tests.rs +++ b/sea-orm-sync/tests/derive_tests.rs @@ -76,10 +76,62 @@ mod postgres_array { use sea_orm::DeriveValueType; #[derive(DeriveValueType)] - pub struct GoodId(i32); + 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 ArrayTest { - pub ingredient_path: Vec, + pub struct IngredientPathRow { + pub ingredient_path: Vec, + pub numeric_label_path: Vec, + pub texture_path: Vec, } }