Diesel Rust ORM

Written — Updated
  • Website: https://diesel.rs/
  • Useful other crates
    • `diesel-derive-enum` helps with translating between Rust enums and database enum types.
    • Although Diesel doesn't support async natively, there are many packages that enable that support with minimal fuss. I've been using `deadpool-diesel`.
  • Selecting into other structures
    • When creating a reusable structure to select into that isn't the normal one, you can derive Selectable and then use type::as_select() to return the list of columns for a select column. Note that this structure must also implement Queryable.
    • This is useful, for example, for structures that include the columns of a database table that the user should be able to see, while omitting internal data.
    • use crate::schema::the_table;
      
      #[derive(Debug, Queryable, Selectable)]
      #[diesel(table_name = the_table)]
      pub struct PublicData {
      	pub id: i32,
          pub name: String,
          pub owner: String,
      }
      
      pub fn get_public_data(id: i32) -> Result<PartialResult, diesel::result::error> {
          the_table::table
            .find(id)
            .select(PartialResult::as_select())
            .first::<PartialResult>()
      }
      
  • JSON Deserializing
    • One of diesel's design decisions is that it will never include any translation code that could fail to serialize or deserialize data from the database. As such, they do not natively support translating from JSON types into Rust structs using the Deserialize attribute. However, this can be implemented generically with a macro, such as this one which converts between the Rust struct and a PostgreSQL JSONB column.
    • #[macro_export]
      macro_rules! diesel_jsonb {
          ($type: ty) => {
              impl ::diesel::deserialize::FromSql<::diesel::sql_types::Jsonb, diesel::pg::Pg> for $type {
                  fn from_sql(
                      value: diesel::backend::RawValue<'_, diesel::pg::Pg>,
                  ) -> diesel::deserialize::Result<Self> {
                      let bytes = value.as_bytes();
                      if bytes[0] != 1 {
                          return Err("Unsupported JSONB encoding version".into());
                      }
      
                      ::serde_json::from_slice(&bytes[1..])
                          .map_err(|e| format!("Invalid JSON: {}", e).into())
                      
                      // This version uses Diesel's internal translation to serde_json::Value instead,
                      // which is simpler but involves an intermediate step of materializing the value as a
                      // serde_json::Value.
                      // let value = <serde_json::Value as ::diesel::deserialize::FromSql<
                      //     ::diesel::sql_types::Jsonb,
                      //     ::diesel::pg::Pg,
                      // >>::from_sql(value)?;
                      // Ok(serde_json::from_value(value)?)
                  }
              }
      
              impl ::diesel::serialize::ToSql<::diesel::sql_types::Jsonb, ::diesel::pg::Pg> for $type {
                  fn to_sql(
                      &self,
                      out: &mut ::diesel::serialize::Output<diesel::pg::Pg>,
                  ) -> diesel::serialize::Result {
                      use std::io::Write;
      
                      out.write_all(&[1])?;
                      serde_json::to_writer(out, self)
                          .map(|_| diesel::serialize::IsNull::No)
                          .map_err(Into::into)
                    
                      // Same as above, using Diesel's internal serde_json::Value support to do most of the work.
                      // let value = serde_json::to_value(self)?;
                      // <serde_json::Value as diesel::serialize::ToSql<
                      //     diesel::sql_types::Jsonb,
                      //     diesel::pg::Pg,
                      // >>::to_sql(&value, &mut out.reborrow())
                  }
              }
          };
      }
      
      

Thanks for reading! If you have any questions or comments, please send me a note on Twitter.