Skip to content

ToSql for NewType structs? #311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
purew opened this issue Dec 31, 2017 · 7 comments
Closed

ToSql for NewType structs? #311

purew opened this issue Dec 31, 2017 · 7 comments

Comments

@purew
Copy link

purew commented Dec 31, 2017

How would one work with new-type structs and ToSql?

See the following example where I'm trying to use pub struct Id(i32):

#[macro_use]
extern crate postgres;
#[macro_use]
extern crate postgres_derive;

use postgres::{Connection, TlsMode};

#[derive(ToSql, FromSql, Debug)]
pub struct Id(pub i32);

#[derive(Debug, ToSql, FromSql)]
#[postgres(name = "person")]
struct Person {
    id: Id,
    name: String,
}

fn main() {
    let conn = Connection::connect(
        "postgres://postgres:pw@localhost:5432/postgres",
        TlsMode::None,
    ).unwrap();

    conn.execute("DROP SCHEMA p CASCADE", &[]);
    conn.execute("CREATE  SCHEMA p", &[]);
    conn.execute(
        "CREATE TYPE p.person AS (
                    id              INT,
                    name            TEXT
                  )",
        &[],
    ).unwrap();
    conn.execute(
        "CREATE TABLE p.persons (
                    id              SERIAL PRIMARY KEY,
                    person          p.person
                  )",
        &[],
    ).unwrap();
    let me = Person {
        id: Id(0),
        //id: 0,
        name: "Steven".to_string(),
    };
    println!("Trying to insert {:?}", me);
    conn.execute("INSERT INTO p.persons (person) VALUES ($1)", &[&me])
        .unwrap();
    for row in &conn.query("SELECT person FROM p.persons", &[]).unwrap() {
        let person: Person = row.get_opt(0).unwrap().unwrap();
        println!("Found person {}", person.name);
    }
}
     Running `target/debug/postgres-new-type-example`
Trying to insert Person { id: Id(0), name: "Steven" }
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error(Conversion(WrongType(Type(Other(Other { name: "person", oid: 18910, kind: Composite([Field { name: "id", type_:
Type(Int4) }, Field { name: "name", type_: Type(Text) }]), schema: "p" })))))', /checkout/src/libcore/result.rs:906:4

@sfackler
Copy link
Owner

rust-postgres-derive treats newtype structs as mapping to postgres domain types. If you want to transparently map through you'll need to implement the traits manually.

@purew
Copy link
Author

purew commented Jan 2, 2018

Ok, gotcha.

@blankhart
Copy link

There is a gotcha with the ToSql instance over a newtype, in that comparison operators in a SELECT statement WHERE clause maps to the underlying Postgres type rather than to the Postgres domain.

So using the example in the documentation defining a SessionId newtype/domain over a BYTEA/Vec<u8> if you were expecting to write something like:

    let select_stmt = client
      .prepare("SELECT * FROM users.test WHERE name = $1 AND session_id = $2")
      .await
      .unwrap();
    let rows = client
      .query(
        &select_stmt,
        &[&"blankhart", &SessionId([0; 16].to_vec())],
      )
      .await
      .unwrap();

you would fail because Postgres expects the underlying BYTEA representation rather than the custom "SessionId" domain.

If you define a newtype mapping to a custom domain you may still need to expose the underlying representation of the Rust type to interact with Postgres in this way.

It seems to me that the ability to derive a transparent ToSql instance of the kind proposed in the PR would be very useful. There may be many occasions where it is convenient to deploy Rust newtypes and smart constructors without enforcing similar invariants in the database via Postgres domains, and in those cases this type of mismatch should go away.

@sfackler
Copy link
Owner

sfackler commented Jul 8, 2020

That is already handled in the generated code: https://github.com/sfackler/rust-postgres/blob/master/postgres-derive/src/fromsql.rs#L98-L110

@blankhart
Copy link

Grateful if you could elaborate - my efforts have so far all generated errors when attempting conversions of this kind using the ToSql instance. For example, the above generates an error unless amended to refer to &SessionId([0; 16].to_vec()).0, i.e. unwrapping the newtype. Also, if the Postgres type is BYTEA , conversion of the Rust type fails with a WrongType error on an INSERT, not just SELECT ... WHERE ....

@sfackler
Copy link
Owner

sfackler commented Jul 8, 2020

Oh - we need to add that extra logic to the ToSql implementation to then it seems. I'll make a fix tonight.

@jbg
Copy link

jbg commented Jan 6, 2021

Did that fix ever get in? I'm seeing an issue that seems the same as described by @blankhart in some of my code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants