Description
Hi!
I get the following error when running a request with composite types:
Protocol("execute: unexpected message: PortalSuspended")
I wrote a minimal reproduction: demurgos@78cb672
Here is the setup code:
CREATE DOMAIN month_id AS INT2 CHECK (1 <= value AND value <= 12);
CREATE TYPE year_month AS (year INT4, month month_id);
CREATE DOMAIN winter_year_month AS year_month CHECK ((value).month <= 3);
CREATE TABLE heating_bills (
month winter_year_month NOT NULL PRIMARY KEY,
cost INT4 NOT NULL
);
This is inspired from real use cases in my applications: a domain type (month_id
) adds some constraints, it is then used in a composite type (year_month
) which is itself behind a domain type (winter_year_month
) to add more constraints.
I implemented the corresponding Rust types encode/decode impls manually:
View encode/decode
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct MonthId(i16);
impl sqlx::Type<Postgres> for MonthId {
fn type_info() -> sqlx::postgres::PgTypeInfo {
sqlx::postgres::PgTypeInfo::with_name("month_id")
}
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
*ty == Self::type_info()
}
}
impl<'r> sqlx::Decode<'r, Postgres> for MonthId {
fn decode(value: sqlx::postgres::PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
Ok(Self(<i16 as sqlx::Decode<Postgres>>::decode(value)?))
}
}
impl<'q> sqlx::Encode<'q, Postgres> for MonthId {
fn encode_by_ref(&self, buf: &mut sqlx::postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
self.0.encode(buf)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct WinterYearMonth {
year: i32,
month: MonthId
}
impl sqlx::Type<Postgres> for WinterYearMonth {
fn type_info() -> sqlx::postgres::PgTypeInfo {
sqlx::postgres::PgTypeInfo::with_name("winter_year_month")
}
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
*ty == Self::type_info()
}
}
impl<'r> sqlx::Decode<'r, Postgres> for WinterYearMonth {
fn decode(value: sqlx::postgres::PgValueRef<'r>) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
let mut decoder = sqlx::postgres::types::PgRecordDecoder::new(value)?;
let year = decoder.try_decode::<i32>()?;
let month = decoder.try_decode::<MonthId>()?;
Ok(Self { year, month })
}
}
impl<'q> sqlx::Encode<'q, Postgres> for WinterYearMonth {
fn encode_by_ref(&self, buf: &mut sqlx::postgres::PgArgumentBuffer) -> sqlx::encode::IsNull {
let mut encoder = sqlx::postgres::types::PgRecordEncoder::new(buf);
encoder.encode(self.year);
encoder.encode(self.month);
encoder.finish();
sqlx::encode::IsNull::No
}
}
And then tried to use them:
let result = sqlx::query("INSERT INTO heating_bills(month, cost) VALUES($1::winter_year_month, 200)")
.bind(WinterYearMonth { year: 2021, month: MonthId(2) })
.execute(&mut conn)
.await;
let result = result.unwrap();
assert_eq!(result.rows_affected(), 1);
This fails at the .unwrap
step with the error Protocol("execute: unexpected message: PortalSuspended")
I believe this error is somehow related to oid cache for user types. The query fails only the first time its used. If I run it again, it succeeds:
// Warm-up the oid cache
sqlx::query("INSERT INTO heating_bills(month, cost) VALUES($1::winter_year_month, 100)")
.bind(WinterYearMonth { year: 2021, month: MonthId(1) })
.execute(&mut conn)
.await;
// Now it succeeds
let result = sqlx::query("INSERT INTO heating_bills(month, cost) VALUES($1::winter_year_month, 200)")
.bind(WinterYearMonth { year: 2021, month: MonthId(2) })
.execute(&mut conn)
.await;
let result = result.unwrap();
assert_eq!(result.rows_affected(), 1);
I intend to look further into the issue and eventually submit a PR. I triggered this error with some complex types, but I believe that this is a more general issue with the communication with the database.