diesel-derive-enum 0.4.0

Derive diesel boilerplate for using enums in databases
Documentation

diesel-derive-enum

crates.io Build Status

This crate allows one to automatically derive the Diesel boilerplate necessary to use Rust enums directly with Postgres and sqlite databases.

v0.3+ requires Diesel 1.1+. For Diesel 1.0 use v0.2.2.

Example usage (Postgres):

# Cargo.toml
[dependencies]
diesel-derive-enum = { version = "0.4", features = ["postgres"] }

// define your enum
#[derive(DbEnum)]
pub enum MyEnum {
    Foo,  // All variants must be fieldless
    Bar,
    BazQuxx,
}

// define your table
table! {
    use diesel::types::Integer;
    use super::MyEnumMapping;
    my_table {
        id -> Integer,
        some_enum -> MyEnumMapping, // Generated Diesel type - see below for explanation
    }
}

// define a struct with which to populate/query the table
#[derive(Insertable, Queryable, Identifiable, Debug, PartialEq)]
#[table_name = "my_table"]
struct  MyRow {
    id: i32,
    some_enum: MyEnum,
}

SQL to create corresponding table:

-- by default the postgres ENUM values correspond to snake_cased Rust enum variant names
CREATE TYPE my_enum AS ENUM ('foo', 'bar', 'baz_quxx');

CREATE TABLE my_table (
  id SERIAL PRIMARY KEY,
  some_enum my_enum NOT NULL
);

Now we can insert and retrieve MyEnum directly:

let data = vec![
    MyRow {
        id: 1,
        some_enum: MyEnum::Foo,
    },
    MyRow {
        id: 2,
        some_enum: MyEnum::BazQuxx,
    },
];
let connection = PgConnection::establish(/*...*/).unwrap();
let inserted = insert_into(my_table::table)
    .values(&data)
    .get_results(&connection)
    .unwrap();
assert_eq!(data, inserted);

See this test for a full working example.

sqlite

sqlite is untyped. Yes, really.. You can store any kind of data in any column and it won't complain. How do we get some nice static checking then? Well... you can't, really, but you can emulate it by add a CHECK to your column definition like so:

CREATE TABLE my_table (
    id SERIAL PRIMARY KEY,
    some_enum TEXT CHECK(my_enum IN ('foo', 'bar', 'baz_quxx')) NOT NULL
);

If you substitute this snippet into the above example, all will be well (and make sure to edit your Cargo.toml to include features = ["sqlite"]). Trivia: the TEXT type annotation isn't even frickin used and you could substitute MY_FAVOURITE_SHINY_TYPE in it's place and it would still work.

Note that it will still be possible to insert other strings (or whatever) into the column 'by hand', though it will be a type error should you attempt to do so through diesel. If you attempt to retreive some other invalid text as an enum, diesel will error at the point of deserialization.

What's up with the naming?

Diesel maintains a set of internal types which correspond one-to-one to the types available in various relational databases. Each internal type then maps to some kind of Rust native type. e.g. diesel::types::Integer maps to i32. So, when we create a new type in Postgres with CREATE TYPE ..., we must also create a corresponding type in Diesel, and then create a mapping to some native Rust type (our enum). Hence there are three types we need to be aware of.

By default, the Postgres and Diesel internal types are inferred from the name of the Rust enum. Specifically, we assume MyEnum corresponds to my_enum in Postgres and MyEnumMapping in Diesel. (The Diesel type is created by the plugin, the Postgres type must be created in SQL).

These defaults can be overridden with the attributes #[PgType = "..."] and #[DieselType = "..."]. (The PgType annotation has no effect on sqlite).

Similarly, we assume that the Postgres ENUM variants are simply the Rust enum variants translated to snake_case. These can be renamed with the inline annotation #[pg_rename = "..."].

See this test for an example of renaming.

print-schema and infer-schema!

The print-schema command (from diesel_cli) attempts to connect to an existing DB and generate a correct mapping of Postgres columns to Diesel internal types. If a custom ENUM exists in the database, Diesel will simply assume that the internal mapping type is the ENUM name, Title-cased (e.g. my_enum -> My_enum). Therefore the derived mapping name must also be corrected with the DieselType attribute e.g. #[DieselType] = "My_enum"].

The infer_schema! macro works similarly but unfortunately is not yet compatible with this crate.

License

Licensed under either of these: