sfackler / rust-postgres

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

Question on mapping Rust types to postgres_types::Type #1096

Closed cwerner87 closed 10 months ago

cwerner87 commented 10 months ago

Hello,

I'm working on a macro to generate boilerplate to annotate structs for the purpose of copying into a Postgres table via BinaryCopyInWriter.

I have some of it completed but I'm struggling a bit on constructing the array of postgres_types::Type for instantiating the writer. I had seen in https://github.com/sfackler/rust-postgres/blob/master/postgres-types/src/lib.rs the table that looks like:

/// | Rust type                         | Postgres type(s)                              |
/// |-----------------------------------|-----------------------------------------------|
/// | `bool`                            | BOOL                                          |
/// | `i8`                              | "char"                                        |
...

And I had incorrectly concluded that there'd be some sort of a match statement that did this mapping. Now that I've been digging into it, it seems that there are macros that generate the to_sql blocks for the various Rust types.

Specifically, in my macro what I'm hoping to do is something like this:

#[derive(ToPgTable)]
struct Product {
  id: i32,
  name: String,
};

Where the macro would then be able to generate the boilerplate for BinaryCopyInWriter and would generate the array of types of [Type::INT4, Type::TEXT] as its second argument.

Does anyone have any advice on how to go about mapping a Rust struct's types to Postgres types? My best idea at this point is to do my own matching, but perhaps there's a superior solution.

sfackler commented 10 months ago

Since that mapping isn't 1-to-1 (for example Rust strings can convert to many Postgres types), you'd probably need to specify the types explicitly in your macro.

cwerner87 commented 10 months ago

Good point - I was planning on having a default that could be overridden (e.g. the default for String might be TEXT, but the caller could override this in the struct definition to be VARCHAR). I guess what I'm asking is: is there any way to leverage existing functionality in this package or do you think that a match is the best solution?

sfackler commented 10 months ago

One other option you could try is to move that mapping from a big match in your macro out into the trait system. Something like

trait DefaultPostgresType {
    const TYPE: Type;
}

impl DefaultPostgresType for i32 {
    const TYPE: Type = Type::INT4;
}

// etc...

And then have your macro-generated code look like <#ty as DefaultPostgresType>::TYPE if the user didn't override it.

cwerner87 commented 10 months ago

Excellent suggestion. That's what I'll do. Thank you!