It seems like the "need" for a additional struct for insert is coming from a general misunderstanding of how inserts in diesel work. Let me try to clarify that (and as I see this question reocurring again and again, please tell me what else should be added to the documentation to make this clearer):
First of all there it is definitively not required to have a separate struct for inserts at all. Let me cite the "All about inserts" guide here (emphasis added by me):
Working with tuples is the typical way to do an insert if you just have some values that you want to stick in the database. But what if your data is coming from another source, like a web form deserialized by Serde? It’d be annoying to have to write (name.eq(user.name), hair_color.eq(user.hair_color))
.
Diesel provides the Insertable trait for this case. Insertable maps your struct to columns in the database. We can derive this automatically by adding #[derive(Insertable)]
to our type.
That means instead of going through all the trouble to create a new struct just for inserts you could just reference the corresponding fields from the original struct directly:
diesel::insert_into(customers::table)
.values((customers::name.eq(&customer.name), …))
.execute(connection)
Now one could argue that it should be possible to just insert the customer struct directly. The answer here is: That's definitively possible, as long as that type implements Insertable
. Any type can implement Insertable
by manually implementing it, which allows to do whatever you want. For structs it's also possible to use the derive, which will then just insert all fields.
Now using #[derive(Insertable)]
on Customer
would insert all fields including the id
field into the database. It seems like you don't want to have this behaviour. In that case there is always to option to manually implement the trait, just like that's the case for #[derive(Debug)]
as well.
Which brings be to the reasoning behind this behaviour: In rust every field must have a value. That means if you insert a new customer you need to set the id field to something to populate the struct. Now you could just set it to some invalid value and ignore that value on insert. This makes it very easy to mix up structs coming from loads and those that are desalinates for inserts. So having this split here just enforces type safety just like you cannot mix String
and Path
easily. Now one could argue that we could just use Option<i32>
there to communicate the absence of a valid ID. That's true for the insert case, but would in turn mean that as soon as you need to access the id of a loaded entity you need to call .unwrap()
or a similar function as you basically know that the id must be there. We as diesel developer feel that this is a bad solution so we've opted for not implementing it for Option<i32>
. That does not mean that you cannot write your own enum Id { Set(i32), Unset}
enum to be used in that location. It just requires implementing a few traits (Insertable
, FromSql
, Queryable
, and FromSqlRow
(the last two can be derived via #[derive(FromSqlRow)
)
For the sake of completeness here is an manually impl of Insertable
that would skip the corresponding field. Knowing which field to skip is user knowledge so nothing the derive could do automatically:
use diesel::prelude::*;
use diesel::dsl;
impl<'a> Insertable<customers::table> for &'a Customer {
type Values = (dsl::Eq<customers::name, &'a str>, …);
fn values(self) -> Self::Values {
(customers::name.eq(&self.name), …)
}
}