From 74dd8aa5070f66ee8fe0c6e6b197b8ab371b32ce Mon Sep 17 00:00:00 2001 From: Huliiiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Mon, 29 Dec 2025 02:03:37 +0800 Subject: [PATCH 1/5] Add missing Array::Null to try_from_value --- src/value/with_array.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/value/with_array.rs b/src/value/with_array.rs index bc05d9194..94ab9c830 100644 --- a/src/value/with_array.rs +++ b/src/value/with_array.rs @@ -29,6 +29,7 @@ macro_rules! impl_value_vec { fn try_from_value(v: Value) -> Result>, ValueTypeErr> { match v { Value::Array(Array::$vari(inner)) => Ok(inner.into_vec()), + Value::Array(Array::Null(ArrayType::$vari)) => Ok(vec![]), _ => Err(ValueTypeErr) } } @@ -106,6 +107,7 @@ impl ValueType for Vec> { fn try_from(v: Value) -> Result { match v { Value::Array(Array::TinyUnsigned(inner)) => Ok(inner.into_vec()), + Value::Array(Array::Null(ArrayType::TinyUnsigned)) => Ok(vec![]), _ => Err(ValueTypeErr), } } @@ -195,7 +197,7 @@ macro_rules! impl_uuid_fmt_pg_array_element { .into_iter() .map(|opt| opt.map(Self::from)) .collect()), - Value::Array(Array::Null(_)) => Ok(vec![]), + Value::Array(Array::Null(ArrayType::Uuid)) => Ok(vec![]), _ => Err(ValueTypeErr), } } From 39d2b350d49deaa70e344ee184a32f04cabf10bd Mon Sep 17 00:00:00 2001 From: Huliiiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Mon, 29 Dec 2025 02:09:29 +0800 Subject: [PATCH 2/5] Make null arrays no longer be treated as empty --- src/value/array.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value/array.rs b/src/value/array.rs index 16b5d76f8..0a4701b39 100644 --- a/src/value/array.rs +++ b/src/value/array.rs @@ -223,7 +223,7 @@ impl Array { Array::IpNetwork(v) => v.is_empty(), #[cfg(feature = "with-mac_address")] Array::MacAddress(v) => v.is_empty(), - Array::Null(_) => true, + Array::Null(_) => false, } } From bb8a2e942f1a3111dec8556febfbce4ed6da8ffa Mon Sep 17 00:00:00 2001 From: Huliiiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Mon, 29 Dec 2025 02:48:52 +0800 Subject: [PATCH 3/5] Fix as_ref_array --- src/value/with_array.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/value/with_array.rs b/src/value/with_array.rs index 94ab9c830..a01ba9077 100644 --- a/src/value/with_array.rs +++ b/src/value/with_array.rs @@ -243,11 +243,15 @@ impl Value { matches!(self, Self::Array(_)) } - pub fn as_ref_array(&self) -> Option<&Array> { + pub fn as_array(&self) -> Option<&Array> { match self { - Self::Array(v) if !v.is_null() => Some(v), - Self::Array(_) => None, - _ => panic!("not Value::Array"), + Self::Array(v) => Some(v), + _ => None, } } + + #[deprecated(since = "1.0.0", note = "Use Value::as_array instead.")] + pub fn as_ref_array(&self) -> Option<&Array> { + self.as_array() + } } From fc97cb39a5861681634d50458b913a0851fe6453 Mon Sep 17 00:00:00 2001 From: Huliiiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Mon, 29 Dec 2025 03:53:51 +0800 Subject: [PATCH 4/5] Clippy --- src/value/array.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/value/array.rs b/src/value/array.rs index 0a4701b39..a454a47a8 100644 --- a/src/value/array.rs +++ b/src/value/array.rs @@ -530,7 +530,7 @@ impl std::fmt::Debug for ArrayIterValue<'_> { } } -impl<'a> Iterator for ArrayIterValue<'a> { +impl Iterator for ArrayIterValue<'_> { type Item = Option; fn next(&mut self) -> Option { From 87ea4c6847215cfc40e348e2ce995708699d19a4 Mon Sep 17 00:00:00 2001 From: Huliiiiii <134658521+Huliiiiii@users.noreply.github.com> Date: Mon, 29 Dec 2025 05:59:54 +0800 Subject: [PATCH 5/5] Add tests --- src/backend/query_builder.rs | 15 +++ src/value/tests.rs | 205 +++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index aa5374564..4ba37f2fc 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -1866,4 +1866,19 @@ mod tests { compare!(date_time_tz, "'2015-06-03 20:34:56.123456 +08:00'"); } + + #[test] + #[cfg(feature = "postgres-array")] + fn prepare_array_null_and_empty() { + use crate::{Array, ArrayType, PostgresQueryBuilder, QueryBuilder, Value}; + + let mut string = String::new(); + PostgresQueryBuilder + .prepare_value(Value::Array(Array::Null(ArrayType::String)), &mut string); + assert_eq!(string, "NULL"); + + string.clear(); + PostgresQueryBuilder.prepare_value(Vec::::new().into(), &mut string); + assert_eq!(string, "'{}'"); + } } diff --git a/src/value/tests.rs b/src/value/tests.rs index 590fa3fbe..181929b12 100644 --- a/src/value/tests.rs +++ b/src/value/tests.rs @@ -424,3 +424,208 @@ fn test_option_array_value() { let out: Option>> = v.unwrap(); assert_eq!(out, None); } + +#[test] +#[cfg(feature = "postgres-array")] +fn test_try_from_value_array() { + fn assert_try_from_value_array(v1: T, v2: T) + where + T: ArrayElement + Clone + PartialEq + std::fmt::Debug, + { + let expected = vec![Some(v1.clone()), None, Some(v2.clone())]; + let value: Value = expected.clone().into(); + let out = T::try_from_value(value).unwrap(); + assert_eq!(out, expected); + + let value = Value::Array(Array::Null(T::ArrayValueType::array_type())); + let out = T::try_from_value(value).unwrap(); + assert_eq!(out, Vec::>::new()); + } + + assert_try_from_value_array(true, false); + assert_try_from_value_array(-1i8, 2); + assert_try_from_value_array(-2i16, 3); + assert_try_from_value_array(-3i32, 4); + assert_try_from_value_array(-4i64, 5); + assert_try_from_value_array(6u16, 7); + assert_try_from_value_array(8u32, 9); + assert_try_from_value_array(10u64, 11); + assert_try_from_value_array(1.5f32, 2.5); + assert_try_from_value_array(3.25f64, 4.75); + assert_try_from_value_array(String::from("a"), String::from("b")); + assert_try_from_value_array('a', 'b'); + assert_try_from_value_array(vec![1, 2, 3], vec![4, 5, 6]); + + #[cfg(feature = "with-json")] + { + assert_try_from_value_array(serde_json::json!({"a": 1}), serde_json::json!({"b": 2})); + } + + #[cfg(feature = "with-chrono")] + { + assert_try_from_value_array( + NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(), + NaiveDate::from_ymd_opt(2021, 2, 2).unwrap(), + ); + assert_try_from_value_array( + NaiveTime::from_hms_opt(1, 2, 3).unwrap(), + NaiveTime::from_hms_opt(4, 5, 6).unwrap(), + ); + assert_try_from_value_array( + NaiveDate::from_ymd_opt(2020, 1, 1) + .unwrap() + .and_hms_opt(1, 2, 3) + .unwrap(), + NaiveDate::from_ymd_opt(2021, 2, 2) + .unwrap() + .and_hms_opt(4, 5, 6) + .unwrap(), + ); + assert_try_from_value_array( + DateTime::::from_utc( + NaiveDate::from_ymd_opt(2020, 1, 1) + .unwrap() + .and_hms_opt(1, 2, 3) + .unwrap(), + Utc, + ), + DateTime::::from_utc( + NaiveDate::from_ymd_opt(2021, 2, 2) + .unwrap() + .and_hms_opt(4, 5, 6) + .unwrap(), + Utc, + ), + ); + assert_try_from_value_array( + chrono::TimeZone::from_utc_datetime( + &Local, + &NaiveDate::from_ymd_opt(2020, 1, 1) + .unwrap() + .and_hms_opt(1, 2, 3) + .unwrap(), + ), + chrono::TimeZone::from_utc_datetime( + &Local, + &NaiveDate::from_ymd_opt(2021, 2, 2) + .unwrap() + .and_hms_opt(4, 5, 6) + .unwrap(), + ), + ); + assert_try_from_value_array( + DateTime::::from_utc( + NaiveDate::from_ymd_opt(2020, 1, 1) + .unwrap() + .and_hms_opt(1, 2, 3) + .unwrap(), + FixedOffset::east_opt(0).unwrap(), + ), + DateTime::::from_utc( + NaiveDate::from_ymd_opt(2021, 2, 2) + .unwrap() + .and_hms_opt(4, 5, 6) + .unwrap(), + FixedOffset::east_opt(0).unwrap(), + ), + ); + } + + #[cfg(feature = "with-time")] + { + assert_try_from_value_array( + time::Date::from_calendar_date(2020, time::Month::January, 1).unwrap(), + time::Date::from_calendar_date(2021, time::Month::February, 2).unwrap(), + ); + assert_try_from_value_array( + time::Time::from_hms(1, 2, 3).unwrap(), + time::Time::from_hms(4, 5, 6).unwrap(), + ); + assert_try_from_value_array( + PrimitiveDateTime::new( + time::Date::from_calendar_date(2020, time::Month::January, 1).unwrap(), + time::Time::from_hms(1, 2, 3).unwrap(), + ), + PrimitiveDateTime::new( + time::Date::from_calendar_date(2021, time::Month::February, 2).unwrap(), + time::Time::from_hms(4, 5, 6).unwrap(), + ), + ); + assert_try_from_value_array( + OffsetDateTime::from_unix_timestamp(0).unwrap(), + OffsetDateTime::from_unix_timestamp(60).unwrap(), + ); + } + + #[cfg(feature = "with-jiff")] + { + assert_try_from_value_array(jiff::civil::date(2020, 1, 1), jiff::civil::date(2021, 2, 2)); + assert_try_from_value_array( + jiff::civil::time(1, 2, 3, 123456 * 1000), + jiff::civil::time(4, 5, 6, 234567 * 1000), + ); + assert_try_from_value_array( + jiff::civil::date(2020, 1, 1).at(1, 2, 3, 123456 * 1000), + jiff::civil::date(2021, 2, 2).at(4, 5, 6, 234567 * 1000), + ); + assert_try_from_value_array( + jiff::Timestamp::constant(0, 123456 * 1000), + jiff::Timestamp::constant(1, 234567 * 1000), + ); + assert_try_from_value_array( + jiff::fmt::strtime::parse( + "%Y-%m-%d %H:%M:%S%.6f %:z", + "1970-01-01 00:00:00.123456 +00:00", + ) + .unwrap() + .to_zoned() + .unwrap(), + jiff::fmt::strtime::parse( + "%Y-%m-%d %H:%M:%S%.6f %:z", + "1970-01-01 00:00:00.234567 +00:00", + ) + .unwrap() + .to_zoned() + .unwrap(), + ); + } + + #[cfg(feature = "with-uuid")] + { + assert_try_from_value_array(Uuid::from_bytes([1; 16]), Uuid::from_bytes([2; 16])); + } + + #[cfg(feature = "with-rust_decimal")] + { + assert_try_from_value_array(Decimal::new(123, 2), Decimal::new(456, 2)); + } + + #[cfg(feature = "with-bigdecimal")] + { + assert_try_from_value_array(BigDecimal::from(123), BigDecimal::from(456)); + } + + #[cfg(feature = "with-ipnetwork")] + { + assert_try_from_value_array( + IpNetwork::new( + std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 0, 1)), + 24, + ) + .unwrap(), + IpNetwork::new( + std::net::IpAddr::V4(std::net::Ipv4Addr::new(10, 0, 0, 1)), + 16, + ) + .unwrap(), + ); + } + + #[cfg(feature = "with-mac_address")] + { + assert_try_from_value_array( + MacAddress::new([0, 1, 2, 3, 4, 5]), + MacAddress::new([6, 7, 8, 9, 10, 11]), + ); + } +}