Fix support for Postgres array of custom types (#1483)

This commit fixes the array decoder to support custom types. The core of the issue was that the array decoder did not use the type info retrieved from the database. It means that it only supported native types.

This commit fixes the issue by using the element type info fetched from the database. A new internal helper method is added to the `PgType` struct: it returns the type info for the inner array element, if available.

Closes #1477
This commit is contained in:
Charles Samborski
2021-12-29 22:05:15 +01:00
committed by GitHub
parent dee514704f
commit 32f1273565
4 changed files with 216 additions and 4 deletions

View File

@@ -16,6 +16,7 @@ use std::sync::Arc;
/// Describes the type of the `pg_type.typtype` column
///
/// See <https://www.postgresql.org/docs/13/catalog-pg-type.html>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum TypType {
Base,
Composite,
@@ -45,6 +46,7 @@ impl TryFrom<u8> for TypType {
/// Describes the type of the `pg_type.typcategory` column
///
/// See <https://www.postgresql.org/docs/13/catalog-pg-type.html#CATALOG-TYPCATEGORY-TABLE>
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum TypCategory {
Array,
Boolean,
@@ -198,7 +200,9 @@ impl PgConnection {
(Ok(TypType::Base), Ok(TypCategory::Array)) => {
Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType {
kind: PgTypeKind::Array(self.fetch_type_by_oid(element).await?),
kind: PgTypeKind::Array(
self.maybe_fetch_type_info_by_oid(element, true).await?,
),
name: name.into(),
oid,
}))))

View File

@@ -1,5 +1,6 @@
#![allow(dead_code)]
use std::borrow::Cow;
use std::fmt::{self, Display, Formatter};
use std::ops::Deref;
use std::sync::Arc;
@@ -750,6 +751,125 @@ impl PgType {
}
}
}
/// If `self` is an array type, return the type info for its element.
///
/// This method should only be called on resolved types: calling it on
/// a type that is merely declared (DeclareWithOid/Name) is a bug.
pub(crate) fn try_array_element(&self) -> Option<Cow<'_, PgTypeInfo>> {
// We explicitly match on all the `None` cases to ensure an exhaustive match.
match self {
PgType::Bool => None,
PgType::BoolArray => Some(Cow::Owned(PgTypeInfo(PgType::Bool))),
PgType::Bytea => None,
PgType::ByteaArray => Some(Cow::Owned(PgTypeInfo(PgType::Bytea))),
PgType::Char => None,
PgType::CharArray => Some(Cow::Owned(PgTypeInfo(PgType::Char))),
PgType::Name => None,
PgType::NameArray => Some(Cow::Owned(PgTypeInfo(PgType::Name))),
PgType::Int8 => None,
PgType::Int8Array => Some(Cow::Owned(PgTypeInfo(PgType::Int8))),
PgType::Int2 => None,
PgType::Int2Array => Some(Cow::Owned(PgTypeInfo(PgType::Int2))),
PgType::Int4 => None,
PgType::Int4Array => Some(Cow::Owned(PgTypeInfo(PgType::Int4))),
PgType::Text => None,
PgType::TextArray => Some(Cow::Owned(PgTypeInfo(PgType::Text))),
PgType::Oid => None,
PgType::OidArray => Some(Cow::Owned(PgTypeInfo(PgType::Oid))),
PgType::Json => None,
PgType::JsonArray => Some(Cow::Owned(PgTypeInfo(PgType::Json))),
PgType::Point => None,
PgType::PointArray => Some(Cow::Owned(PgTypeInfo(PgType::Point))),
PgType::Lseg => None,
PgType::LsegArray => Some(Cow::Owned(PgTypeInfo(PgType::Lseg))),
PgType::Path => None,
PgType::PathArray => Some(Cow::Owned(PgTypeInfo(PgType::Path))),
PgType::Box => None,
PgType::BoxArray => Some(Cow::Owned(PgTypeInfo(PgType::Box))),
PgType::Polygon => None,
PgType::PolygonArray => Some(Cow::Owned(PgTypeInfo(PgType::Polygon))),
PgType::Line => None,
PgType::LineArray => Some(Cow::Owned(PgTypeInfo(PgType::Line))),
PgType::Cidr => None,
PgType::CidrArray => Some(Cow::Owned(PgTypeInfo(PgType::Cidr))),
PgType::Float4 => None,
PgType::Float4Array => Some(Cow::Owned(PgTypeInfo(PgType::Float4))),
PgType::Float8 => None,
PgType::Float8Array => Some(Cow::Owned(PgTypeInfo(PgType::Float8))),
PgType::Circle => None,
PgType::CircleArray => Some(Cow::Owned(PgTypeInfo(PgType::Circle))),
PgType::Macaddr8 => None,
PgType::Macaddr8Array => Some(Cow::Owned(PgTypeInfo(PgType::Macaddr8))),
PgType::Money => None,
PgType::MoneyArray => Some(Cow::Owned(PgTypeInfo(PgType::Money))),
PgType::Macaddr => None,
PgType::MacaddrArray => Some(Cow::Owned(PgTypeInfo(PgType::Macaddr))),
PgType::Inet => None,
PgType::InetArray => Some(Cow::Owned(PgTypeInfo(PgType::Inet))),
PgType::Bpchar => None,
PgType::BpcharArray => Some(Cow::Owned(PgTypeInfo(PgType::Bpchar))),
PgType::Varchar => None,
PgType::VarcharArray => Some(Cow::Owned(PgTypeInfo(PgType::Varchar))),
PgType::Date => None,
PgType::DateArray => Some(Cow::Owned(PgTypeInfo(PgType::Date))),
PgType::Time => None,
PgType::TimeArray => Some(Cow::Owned(PgTypeInfo(PgType::Time))),
PgType::Timestamp => None,
PgType::TimestampArray => Some(Cow::Owned(PgTypeInfo(PgType::Timestamp))),
PgType::Timestamptz => None,
PgType::TimestamptzArray => Some(Cow::Owned(PgTypeInfo(PgType::Timestamptz))),
PgType::Interval => None,
PgType::IntervalArray => Some(Cow::Owned(PgTypeInfo(PgType::Interval))),
PgType::Timetz => None,
PgType::TimetzArray => Some(Cow::Owned(PgTypeInfo(PgType::Timetz))),
PgType::Bit => None,
PgType::BitArray => Some(Cow::Owned(PgTypeInfo(PgType::Bit))),
PgType::Varbit => None,
PgType::VarbitArray => Some(Cow::Owned(PgTypeInfo(PgType::Varbit))),
PgType::Numeric => None,
PgType::NumericArray => Some(Cow::Owned(PgTypeInfo(PgType::Numeric))),
PgType::Record => None,
PgType::RecordArray => Some(Cow::Owned(PgTypeInfo(PgType::Record))),
PgType::Uuid => None,
PgType::UuidArray => Some(Cow::Owned(PgTypeInfo(PgType::Uuid))),
PgType::Jsonb => None,
PgType::JsonbArray => Some(Cow::Owned(PgTypeInfo(PgType::Jsonb))),
PgType::Int4Range => None,
PgType::Int4RangeArray => Some(Cow::Owned(PgTypeInfo(PgType::Int4Range))),
PgType::NumRange => None,
PgType::NumRangeArray => Some(Cow::Owned(PgTypeInfo(PgType::NumRange))),
PgType::TsRange => None,
PgType::TsRangeArray => Some(Cow::Owned(PgTypeInfo(PgType::TsRange))),
PgType::TstzRange => None,
PgType::TstzRangeArray => Some(Cow::Owned(PgTypeInfo(PgType::TstzRange))),
PgType::DateRange => None,
PgType::DateRangeArray => Some(Cow::Owned(PgTypeInfo(PgType::DateRange))),
PgType::Int8Range => None,
PgType::Int8RangeArray => Some(Cow::Owned(PgTypeInfo(PgType::Int8Range))),
PgType::Jsonpath => None,
PgType::JsonpathArray => Some(Cow::Owned(PgTypeInfo(PgType::Jsonpath))),
// There is no `UnknownArray`
PgType::Unknown => None,
// There is no `VoidArray`
PgType::Void => None,
PgType::Custom(ty) => match &ty.kind {
PgTypeKind::Simple => None,
PgTypeKind::Pseudo => None,
PgTypeKind::Domain(_) => None,
PgTypeKind::Composite(_) => None,
PgTypeKind::Array(ref elem_type_info) => Some(Cow::Borrowed(elem_type_info)),
PgTypeKind::Enum(_) => None,
PgTypeKind::Range(_) => None,
},
PgType::DeclareWithOid(oid) => {
unreachable!("(bug) use of unresolved type declaration [oid={}]", oid);
}
PgType::DeclareWithName(name) => {
unreachable!("(bug) use of unresolved type declaration [name={}]", name);
}
}
}
}
impl TypeInfo for PgTypeInfo {

View File

@@ -1,4 +1,5 @@
use bytes::Buf;
use std::borrow::Cow;
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
@@ -103,7 +104,6 @@ where
T: for<'a> Decode<'a, Postgres> + Type<Postgres>,
{
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
let element_type_info;
let format = value.format();
match format {
@@ -131,7 +131,8 @@ where
// the OID of the element
let element_type_oid = buf.get_u32();
element_type_info = PgTypeInfo::try_from_oid(element_type_oid)
let element_type_info: PgTypeInfo = PgTypeInfo::try_from_oid(element_type_oid)
.or_else(|| value.type_info.try_array_element().map(Cow::into_owned))
.unwrap_or_else(|| PgTypeInfo(PgType::DeclareWithOid(element_type_oid)));
// length of the array axis
@@ -159,7 +160,7 @@ where
PgValueFormat::Text => {
// no type is provided from the database for the element
element_type_info = T::type_info();
let element_type_info = T::type_info();
let s = value.as_str()?;