Skip to content

Postgres: Unexpected PortalSuspended when binding domain type of composite type of domain type #1110

Closed
@demurgos

Description

@demurgos

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions