I have an SQL table that I want to work with through Diesel:
CREATE TABLE records (
id BIGSERIAL PRIMARY KEY,
record_type SMALLINT NOT NULL,
value DECIMAL(10, 10) NOT NULL
)
This table generates the following schema:
table! {
records (id) {
id -> Int8,
record_type -> Int2,
value -> Numeric,
}
}
Diesel exports decimals as bigdecimal::BigDecimal
, but I'd like to work with decimal::d128
instead. I also want to map record_type
to an enum, so I declare my model like this:
use decimal::d128;
pub enum RecordType {
A,
B,
}
pub struct Record {
pub id: i64,
pub record_type: RecordType,
pub value: d128,
}
I can't use #derive(Queryable, Insertable)
because of non-standard type mapping, so I try to implement these traits myself:
impl Queryable<records::SqlType, Pg> for Record {
type Row = (i64, i16, BigDecimal);
fn build(row: Self::Row) -> Self {
Record {
id: row.0,
record_type: match row.1 {
1 => RecordType::A,
2 => RecordType::B,
_ => panic!("Wrong record type"),
},
value: d128!(format!("{}", row.2)),
}
}
}
I can't figure out how to implement Insertable
. What is the Values
associated type? Diesel's documentation is not very clear on this.
Maybe there's a better way to achieve what I'm trying to do?
Cargo.toml
:
[dependencies]
bigdecimal = "0.0.10"
decimal = "2.0.4"
diesel = { version = "1.1.1", features = ["postgres", "bigdecimal", "num-bigint", "num-integer", "num-traits"] }
dotenv = "0.9.0"
I find it more convenient to create newtype wrappers that implement ToSql
and FromSql
. You can then build with these basic blocks to create larger types that can derive Queryable
/ Insertable
.
This example only shows how to perform the mapping of the enum to and from a SmallInt
, but the case for the decimal would be the same. The only difference would be in how you perform the transformations:
#[macro_use]
extern crate diesel;
mod types {
use diesel::sql_types::*;
use diesel::backend::Backend;
use diesel::deserialize::{self, FromSql};
use diesel::serialize::{self, ToSql, Output};
use std::io;
table! {
records (id) {
id -> BigInt,
record_type -> SmallInt,
}
}
#[derive(Debug, Copy, Clone, AsExpression, FromSqlRow)]
#[sql_type = "SmallInt"]
pub enum RecordType {
A,
B,
}
impl<DB: Backend> ToSql<SmallInt, DB> for RecordType
where
i16: ToSql<SmallInt, DB>,
{
fn to_sql<W>(&self, out: &mut Output<W, DB>) -> serialize::Result
where
W: io::Write,
{
let v = match *self {
RecordType::A => 1,
RecordType::B => 2,
};
v.to_sql(out)
}
}
impl<DB: Backend> FromSql<SmallInt, DB> for RecordType
where
i16: FromSql<SmallInt, DB>,
{
fn from_sql(bytes: Option<&DB::RawValue>) -> deserialize::Result<Self> {
let v = i16::from_sql(bytes)?;
Ok(match v {
1 => RecordType::A,
2 => RecordType::B,
_ => return Err("replace me with a real error".into()),
})
}
}
#[derive(Insertable, Queryable, Debug)]
#[table_name = "records"]
pub struct Record {
pub id: i64,
pub record_type: RecordType,
}
}
There's a draft guide describing all the derives and their annotations, but it doesn't yet mention #[sql_type]
for an entire type. This lets Diesel know what kind of underlying storage is needed inside of the database.
See also the Diesel tests for custom types.