[Diesel] use of #[derive(Insertable)] causes a compile error?

I have a Diesel project that almost works (i.e. I can generate my migration, I have a Queryable struct etc), except for this piece, which fails to compile:

#[derive(Debug, Clone, Insertable)]
#[table_name = "msgs"]
pub struct InsertableMsg {
    process: String,

    revision: u64,

    kind: String,

    origin: Option<String>,

    sent_at_ns: u64,

    received_at_ns: u64,

    wire_time_ns: u64,
}

It fails with this error:

error[E0277]: the trait bound `u64: diesel::Expression` is not satisfied
  --> src/bin/amplify/visualization_server.rs:83:24
   |
83 | #[derive(Debug, Clone, Insertable)]
   |                        ^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `u64`
   |
   = note: required because of the requirements on the impl of `diesel::Expression` for `&'insert u64`
   = note: required because of the requirements on the impl of `diesel::expression::AsExpression<diesel::types::Integer>` for `&'insert u64`
   = note: this error originates in a macro outside of the current crate

There are more errors, but this is the baffling one.

I was under the impression I could actually define an Insertable struct however I see fit, so this makes no sense to me.
Can anyone explain what's going on here?

And as a follow-up question, how would this problem be solved for a field of some user-defined type?

EDIT: I've narrowed the issue to something akin to diesel does not like Rust's large unsized integer types, since the code compiles if I change the type to i32, yet usize and u32 cause compile errors, like u64. Is there really no way to define a table + struct such that I can write a u64 value to a sqlite db with diesel?

I think I found the problem, which is actually split into 2 parts.

Part I: I use the infer_schema! macro, which means that the schemas defined in the migrations directory effectively become the definitions. The corresponding fields were all of type INTEGER, rather than type UNSIGNED BIG INT. Correcting this mistake cleared up all compile errors.

Part II: The u64 type appears to be unsupported. So I compromised by making the Insertable struct field type i64 instead.

For PostgreSQL BigInt seems to be supported just fine: PostgreSQL: Documentation: 9.1: Numeric Types

But I am getting the same issues as you are: Migrating to BigInt, same effect, even if postgresql supports bigint … · brainstorm/htsget-indexer@54a2ce8 · GitHub, namely:

error[E0277]: the trait bound `diesel::sql_types::BigInt: diesel::Expression` is not satisfied
 --> src/store/models.rs:4:21
  |
4 | #[derive(Queryable, Insertable)]
  |                     ^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `diesel::sql_types::BigInt`
  |
  = note: required because of the requirements on the impl of `diesel::expression::AsExpression<diesel::sql_types::BigInt>` for `diesel::sql_types::BigInt`

What is the current approach (almost year 2020 now :slight_smile:) to get Diesel to accept those bignum types?

the trait bound `diesel::sql_types::BigInt: diesel::Expression` is not satisfied

Did you put diesel::sql_types::BigInt into your struct directly? You aren't supposed to use it directly, as it is just used to mark which types can be put into a postgres bigint column.

As the documentation says here, the only choice for this is i64, so use that type instead.

Thanks Alice! That's exactly right and what I ended up doing:

https://github.com/brainstorm/htsget-indexer/commit/471b48534fce1c8e390b76a25097de04f03ddbe6

But I find it to be a bit counterintuitive, right? I would like SQL types to match as closely as possible to my structs... OTOH, I recognise that keeping consistent mappings for each type can be a maintainer's nightmare?

In this case the type that matches as close as possible is i64. Unfortunately these marker types are a necessary evil as not all sql types match up one-to-one to rust types. The best example of this is the Timestamp type which has three different timestamp types it might convert to.

Additionally by implementing the FromSql or ToSql traits on your own types, you would be able to use those directly in your Insertable or Queryable types. I have personally used this for e.g. a UserID struct which just contained the correct integer type.

2 Likes