sfackler / rust-postgres

Native PostgreSQL driver for the Rust programming language
Apache License 2.0
3.47k stars 439 forks source link

Using Enums with query_typed #1180

Open alex-richman-onesignal opened 3 weeks ago

alex-richman-onesignal commented 3 weeks ago

Hi,

I'm converting some code from query to query_typed, so it can sit behind a transactional pooler.

I have a query that looks like this, whcih works fine:

conn.query("SELECT * FROM macguffins WHERE kind = $1;", &[&kind]).await

And coverted it to query_typed by adding Type::ANYENUM, which is the only enum related type I could find i n the crate:

conn.query_typed("SELECT * FROM macguffins WHERE kind = $1;", &[(&kind, Type::ANYENUM)]).await

But this does not work, throwing:

error serializing parameter 0: cannot convert between the Rust type `macguffin_factory::postgres::MacguffinKind` and the Postgres type `anyenum

MacguffinKind looks like:

#[derive(Debug, FromSql, ToSql, Clone, Copy)]
#[postgres(name = "macguffin_kind")]
pub enum MacguffinKind {
    #[postgres(name = "blue")]
    Blue,
    #[postgres(name = "Green")]
    Green,
}

I tried adding allow_mismatch to the enum derive, but no dice.

What's the right way to use Enums with query_typed?

Thanks!

sfackler commented 3 weeks ago

You can extract a Type for user-defined Postgres types by preparing a query and pulling it out of the Statement. Something like this (untested):

let stmt = client.prepare("SELECT $1::macguffin_kind")?;
let macguffin_kind_type = stmt.params()[0].clone();
alex-richman-onesignal commented 3 weeks ago

Thanks, this is interesting -- I was able to get it working like this:

let statement = conn.prepare("SELECT $1::macguffin_kind;").await?;
let macguffin_kind_type = stmt.params()[0].clone();

Where macguffin_kind_type came back as:

Other(Other { name: "macguffin_kind", oid: 33131, kind: Enum(["blue", "green"]), schema: "public" })

Seems like we should also be able to build that Other type ourselves without calling a prepare, something like this is accepted:

Type::new("macguffin_kind".to_string(), 33131, Kind::Enum(vec!["blue".to_string(), "green".to_string()]), "public".to_string());

I suppose the issue being that the OID will be different per server. However even if I set the oid to 0 it's still accepted and works in the query so maybe it's not required for this specific case?

Either way, it would be cool if this were somehow baked into the FormSql/ToSql derives to make the query_typed a little easier to use.

sfackler commented 3 weeks ago

Setting the OID to 0 is the same thing as not using query_typed. The whole point of that method vs query is that you are telling the server the specific type OIDs of each query parameter.