diff --git a/sqlx-postgres/src/type_info.rs b/sqlx-postgres/src/type_info.rs index 28c56758e9..50504c1174 100644 --- a/sqlx-postgres/src/type_info.rs +++ b/sqlx-postgres/src/type_info.rs @@ -1013,7 +1013,7 @@ impl PgType { /// If `soft_eq` is true and `self` or `other` is `DeclareWithOid` but not both, return `true` /// before checking names. fn eq_impl(&self, other: &Self, soft_eq: bool) -> bool { - if let (Some(a), Some(b)) = (self.try_oid(), other.try_oid()) { + if let (Some(a), Some(b)) = (self.try_base_oid(), other.try_base_oid()) { // If there are OIDs available, use OIDs to perform a direct match return a == b; } @@ -1035,6 +1035,18 @@ impl PgType { // Otherwise, perform a match on the name name_eq(self.name(), other.name()) } + + // Tries to return the OID of the type, returns the OID of the base_type for domain types + #[inline(always)] + fn try_base_oid(&self) -> Option { + match self { + PgType::Custom(custom) => match &custom.kind { + PgTypeKind::Domain(domain) => domain.try_oid(), + _ => Some(custom.oid), + }, + ty => ty.try_oid(), + } + } } impl TypeInfo for PgTypeInfo { diff --git a/sqlx-postgres/src/types/record.rs b/sqlx-postgres/src/types/record.rs index 6e37182c40..a5410caadd 100644 --- a/sqlx-postgres/src/types/record.rs +++ b/sqlx-postgres/src/types/record.rs @@ -103,27 +103,7 @@ impl<'r> PgRecordDecoder<'r> { match self.fmt { PgValueFormat::Binary => { let element_type_oid = Oid(self.buf.get_u32()); - let element_type_opt = match self.typ.0.kind() { - PgTypeKind::Simple if self.typ.0 == PgType::Record => { - PgTypeInfo::try_from_oid(element_type_oid) - } - - PgTypeKind::Composite(fields) => { - let ty = fields[self.ind].1.clone(); - if ty.0.oid() != element_type_oid { - return Err("unexpected mismatch of composite type information".into()); - } - - Some(ty) - } - - _ => { - return Err( - "unexpected non-composite type being decoded as a composite type" - .into(), - ); - } - }; + let element_type_opt = self.find_type_info(&self.typ, element_type_oid)?; if let Some(ty) = &element_type_opt { if !ty.is_null() && !T::compatible(ty) { @@ -202,4 +182,24 @@ impl<'r> PgRecordDecoder<'r> { } } } + + fn find_type_info( + &self, + typ: &PgTypeInfo, + oid: Oid, + ) -> Result, BoxDynError> { + match typ.kind() { + PgTypeKind::Simple if typ.0 == PgType::Record => Ok(PgTypeInfo::try_from_oid(oid)), + PgTypeKind::Composite(fields) => { + let ty = fields[self.ind].1.clone(); + if ty.0.oid() != oid { + return Err("unexpected mismatch of composite type information".into()); + } + + Ok(Some(ty)) + } + PgTypeKind::Domain(domain) => self.find_type_info(domain, oid), + _ => Err("unexpected custom type being decoded as a composite type".into()), + } + } } diff --git a/tests/postgres/setup.sql b/tests/postgres/setup.sql index 4d89062799..65487b8ae5 100644 --- a/tests/postgres/setup.sql +++ b/tests/postgres/setup.sql @@ -68,3 +68,16 @@ CREATE TABLE circles ( c circle, EXCLUDE USING gist (c WITH &&) ); + +CREATE DOMAIN positive_int AS integer CHECK (VALUE >= 0); +CREATE DOMAIN percentage AS positive_int CHECK (VALUE <= 100); + +CREATE TYPE person as ( + id int, + age positive_int, + percent percentage +); + +CREATE TYPE leaf_composite AS (prim integer); +CREATE DOMAIN domain AS leaf_composite; +CREATE TYPE root_composite AS (domain domain); diff --git a/tests/postgres/types.rs b/tests/postgres/types.rs index 8a9288ba75..16a165278b 100644 --- a/tests/postgres/types.rs +++ b/tests/postgres/types.rs @@ -697,6 +697,45 @@ test_type!(ltree_vec>(Postgres, ] )); +#[derive(sqlx::Type, Debug, PartialEq)] +#[sqlx(type_name = "positive_int")] +struct PositiveInt(i32); + +#[derive(sqlx::Type, Debug, PartialEq)] +#[sqlx(type_name = "percentage")] +struct Percentage(PositiveInt); + +#[derive(sqlx::Type, Debug, PartialEq)] +struct Person { + id: i32, + age: PositiveInt, + percent: Percentage, +} + +test_type!(nested_domain_types_1(Postgres, + "ROW(1, 21::positive_int, 50::percentage)::person" == Person { id: 1, age: PositiveInt(21), percent: Percentage(PositiveInt(50)) }) +); + +#[derive(sqlx::Type, Debug, PartialEq)] +#[sqlx(type_name = "leaf_composite")] +struct LeafComposite { + prim: i32, +} + +#[derive(sqlx::Type, Debug, PartialEq)] +#[sqlx(type_name = "domain")] +struct Domain(LeafComposite); + +#[derive(sqlx::Type, Debug, PartialEq)] +#[sqlx(type_name = "root_composite")] +struct RootComposite { + domain: Domain, +} + +test_type!(nested_domain_types_2(Postgres, + "ROW(ROW(1))::root_composite" == RootComposite { domain: Domain(LeafComposite { prim: 1})}) +); + test_type!(test_arc>(Postgres, "1::INT4" == Arc::new(1i32))); test_type!(test_cow>(Postgres, "1::INT4" == Cow::::Owned(1i32))); test_type!(test_box>(Postgres, "1::INT4" == Box::new(1i32)));