Skip to content

Commit

Permalink
Merge pull request #4364 from Jaltaire/master
Browse files Browse the repository at this point in the history
Add a skip_update field attribute for #[derive(AsChangeset)] (#860)
  • Loading branch information
weiznich authored Dec 5, 2024
2 parents e832adb + 986ede8 commit b3cca0b
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Increasing the minimal supported Rust version will always be coupled at least wi
* Added `limit()` and `offset()` DSL to combination clauses such as `UNION`
* Fixed `#[derive(Identifiable)]` ignoring attribute `#[diesel(serialize_as)]` on primary keys
* Added embedded struct support for `AsChangeset` via `#[diesel(embed)]`
* Added a `#[diesel(skip_update)]` attribute for the `AsChangeset` derive to skip updating a field present in the struct
* Support for libsqlite3-sys 0.30.0
* Add support for built-in PostgreSQL range operators and functions
* Support for postgres multirange type
Expand Down
4 changes: 4 additions & 0 deletions diesel_derives/src/as_changeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ pub fn derive(item: DeriveInput) -> Result<TokenStream> {
let mut ref_field_assign = Vec::with_capacity(fields_for_update.len());

for field in fields_for_update {
// skip this field while generating the update
if field.skip_update() {
continue;
}
// Use field-level attr. with fallback to the struct-level one.
let treat_none_as_null = match &field.treat_none_as_null {
Some(attr) => {
Expand Down
3 changes: 3 additions & 0 deletions diesel_derives/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct AttributeSpanWrapper<T> {
pub enum FieldAttr {
Embed(Ident),
SkipInsertion(Ident),
SkipUpdate(Ident),

ColumnName(Ident, SqlIdentifier),
SqlType(Ident, TypePath),
Expand Down Expand Up @@ -145,6 +146,7 @@ impl Parse for FieldAttr {
match &*name_str {
"embed" => Ok(FieldAttr::Embed(name)),
"skip_insertion" => Ok(FieldAttr::SkipInsertion(name)),
"skip_update" => Ok(FieldAttr::SkipUpdate(name)),

"column_name" => Ok(FieldAttr::ColumnName(
name,
Expand Down Expand Up @@ -199,6 +201,7 @@ impl MySpanned for FieldAttr {
match self {
FieldAttr::Embed(ident)
| FieldAttr::SkipInsertion(ident)
| FieldAttr::SkipUpdate(ident)
| FieldAttr::ColumnName(ident, _)
| FieldAttr::SqlType(ident, _)
| FieldAttr::TreatNoneAsNull(ident, _)
Expand Down
14 changes: 14 additions & 0 deletions diesel_derives/src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct Field {
pub select_expression_type: Option<AttributeSpanWrapper<Type>>,
pub embed: Option<AttributeSpanWrapper<bool>>,
pub skip_insertion: Option<AttributeSpanWrapper<bool>>,
pub skip_update: Option<AttributeSpanWrapper<bool>>,
}

impl Field {
Expand All @@ -32,6 +33,7 @@ impl Field {
let mut deserialize_as = None;
let mut embed = None;
let mut skip_insertion = None;
let mut skip_update = None;
let mut select_expression = None;
let mut select_expression_type = None;
let mut treat_none_as_default_value = None;
Expand Down Expand Up @@ -111,6 +113,13 @@ impl Field {
ident_span,
})
}
FieldAttr::SkipUpdate(_) => {
skip_update = Some(AttributeSpanWrapper {
item: true,
attribute_span,
ident_span,
})
}
}
}

Expand Down Expand Up @@ -138,6 +147,7 @@ impl Field {
select_expression_type,
embed,
skip_insertion,
skip_update,
})
}

Expand Down Expand Up @@ -174,6 +184,10 @@ impl Field {
.map(|a| a.item)
.unwrap_or(false)
}

pub(crate) fn skip_update(&self) -> bool {
self.skip_update.as_ref().map(|a| a.item).unwrap_or(false)
}
}

pub enum FieldName {
Expand Down
2 changes: 2 additions & 0 deletions diesel_derives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ mod valid_grouping;
/// the actual field type.
/// * `#[diesel(treat_none_as_null = true/false)]`, overrides the container-level
/// `treat_none_as_null` attribute for the current field.
/// * `#[diesel(skip_update)]`, skips updating this field. Useful for working with
/// generated columns.
#[cfg_attr(
all(not(feature = "without-deprecated"), feature = "with-deprecated"),
proc_macro_derive(
Expand Down
37 changes: 37 additions & 0 deletions diesel_derives/tests/as_changeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,3 +891,40 @@ fn embedded_struct() {
let actual = users::table.order(users::id).load(connection);
assert_eq!(Ok(expected), actual);
}

#[test]
fn skip_update() {
let connection = &mut connection_with_sean_and_tess_in_users_table();

#[derive(AsChangeset)]
#[diesel(table_name = users)]
#[diesel(primary_key(name))]
#[allow(dead_code)]
struct UserForm<'a> {
#[diesel(skip_update)]
name: &'a str,
hair_color: &'a str,
r#type: &'a str,
#[diesel(skip_update)]
non_existing: i32,
}

diesel::update(users::table.find(1))
.set(UserForm {
name: "John",
hair_color: "green",
r#type: "admin",
non_existing: 42,
})
.execute(connection)
.unwrap();

let res = users::table
.find(1)
.first::<(i32, String, Option<String>, Option<String>)>(connection)
.unwrap();

assert_eq!(res.1, "Sean");
assert_eq!(res.2, Some("green".into()));
assert_eq!(res.3, Some("admin".into()));
}

0 comments on commit b3cca0b

Please sign in to comment.