Implement AsChangeset using a stable procedural macro

This is slightly more complicated that the previous macros, as we have
the additional `treat_none_as_null` option. This should behave
identically to the procedural macro
This commit is contained in:
Sean Griffin 2016-05-13 10:17:41 -04:00
parent f054710da1
commit d9cabcc230
3 changed files with 271 additions and 0 deletions

View File

@ -19,6 +19,10 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
those wishing to avoid syntax extensions from `diesel_codegen`. See
http://docs.diesel.rs/diesel/macro.Identifiable!.html for details.
* The `AsChangeset!` macro can now be used instead of `#[changeset_for(table)]`
for those wishing to avoid syntax extensions from `diesel_codegen`. See
http://docs.diesel.rs/diesel/macro.AsChangeset!.html for details.
* Added support for the PostgreSQL `ALL` operator. See
http://docs.diesel.rs/diesel/pg/expression/dsl/fn.all.html for details.

View File

@ -0,0 +1,266 @@
#[macro_export]
macro_rules! AsChangeset {
// Provide a default value for treat_none_as_null if not provided
(
($table_name:ident)
$($body:tt)*
) => {
AsChangeset! {
($table_name, treat_none_as_null="false")
$($body)*
}
};
// Strip meta items, pub (if present) and struct from definition
(
$args:tt
$(#[$ignore:meta])*
$(pub)* struct $($body:tt)*
) => {
AsChangeset! {
$args
$($body)*
}
};
// Handle struct with lifetimes
(
($table_name:ident, treat_none_as_null=$treat_none_as_null:tt)
$struct_name:ident <$($lifetime:tt),*>
$body:tt $(;)*
) => {
__diesel_parse_struct_body! {
(
struct_name = $struct_name,
table_name = $table_name,
treat_none_as_null = $treat_none_as_null,
struct_ty = $struct_name<$($lifetime),*>,
lifetimes = ($($lifetime),*),
),
callback = AsChangeset,
body = $body,
}
};
// Handle struct with no lifetimes. We pass a dummy lifetime to reduce
// the amount of branching later.
(
($table_name:ident, treat_none_as_null=$treat_none_as_null:tt)
$struct_name:ident
$body:tt $(;)*
) => {
__diesel_parse_struct_body! {
(
struct_name = $struct_name,
table_name = $table_name,
treat_none_as_null = $treat_none_as_null,
struct_ty = $struct_name,
lifetimes = ('a),
),
callback = AsChangeset,
body = $body,
}
};
// Receive parsed fields of struct from `__diesel_parse_struct_body`
(
(
struct_name = $struct_name:ident,
table_name = $table_name:ident,
treat_none_as_null = $treat_none_as_null:tt,
$($headers:tt)*
),
fields = $fields:tt,
) => {
AsChangeset_construct_changeset_ty! {
(
fields = $fields,
struct_name = $struct_name,
table_name = $table_name,
treat_none_as_null = $treat_none_as_null,
$($headers)*
),
table_name = $table_name,
treat_none_as_null = $treat_none_as_null,
fields = $fields,
changeset_ty = (),
}
};
// Receive changeset ty when tuple struct from `AsChangeset_construct_changeset_ty`
(
(
fields = [$({
column_name: $column_name:ident,
field_ty: $field_ty:ty,
field_kind: $field_kind:ident,
})+],
struct_name = $struct_name:ident,
$($headers:tt)*
),
changeset_ty = $changeset_ty:ty,
) => {
AsChangeset! {
$($headers)*
self_to_columns = $struct_name($(ref $column_name),+),
columns = ($($column_name, $field_kind),+),
field_names = [],
changeset_ty = $changeset_ty,
}
};
// Receive changeset ty when named struct from `AsChangeset_construct_changeset_ty`
(
(
fields = [$({
field_name: $field_name:ident,
column_name: $column_name:ident,
field_ty: $field_ty:ty,
field_kind: $field_kind:ident,
})+],
struct_name = $struct_name:ident,
$($headers:tt)*
),
changeset_ty = $changeset_ty:ty,
) => {
AsChangeset! {
$($headers)*
self_to_columns = $struct_name { $($field_name: ref $column_name),+ },
columns = ($($column_name, $field_kind),+),
field_names = [$($field_name)+],
changeset_ty = $changeset_ty,
}
};
// Construct final impl
(
table_name = $table_name:ident,
treat_none_as_null = $treat_none_as_null:tt,
struct_ty = $struct_ty:ty,
lifetimes = ($($lifetime:tt),*),
self_to_columns = $self_to_columns:pat,
columns = ($($column_name:ident, $field_kind:ident),+),
field_names = $field_names:tt,
changeset_ty = $changeset_ty:ty,
) => {
__diesel_parse_as_item! {
impl<$($lifetime: 'update,)* 'update> $crate::query_builder::AsChangeset
for &'update $struct_ty
{
type Target = $table_name::table;
type Changeset = $changeset_ty;
#[allow(non_shorthand_field_patterns)]
fn as_changeset(self) -> Self::Changeset {
let $self_to_columns = *self;
($(
AsChangeset_column_expr!(
$table_name::$column_name,
$column_name,
none_as_null = $treat_none_as_null,
field_kind = $field_kind,
)
,)+)
}
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! AsChangeset_construct_changeset_ty {
// Handle option field when treat none as null is false
(
$headers:tt,
table_name = $table_name:ident,
treat_none_as_null = "false",
fields = [{
$(field_name: $field_name:ident,)*
column_name: $column_name:ident,
field_ty: Option<$field_ty:ty>,
field_kind: option,
} $($tail:tt)*],
changeset_ty = ($($changeset_ty:ty,)*),
) => {
AsChangeset_construct_changeset_ty! {
$headers,
table_name = $table_name,
treat_none_as_null = "false",
fields = [$($tail)*],
changeset_ty = ($($changeset_ty,)*
Option<$crate::expression::predicates::Eq<
$table_name::$column_name,
$crate::expression::bound::Bound<
<$table_name::$column_name as $crate::expression::Expression>::SqlType,
&'update $field_ty,
>,
>>,
),
}
};
// Handle normal field or option when treat none as null is true
(
$headers:tt,
table_name = $table_name:ident,
treat_none_as_null = $treat_none_as_null:tt,
fields = [{
$(field_name: $field_name:ident,)*
column_name: $column_name:ident,
field_ty: $field_ty:ty,
$($ignore:tt)*
} $($tail:tt)*],
changeset_ty = ($($changeset_ty:ty,)*),
) => {
AsChangeset_construct_changeset_ty! {
$headers,
table_name = $table_name,
treat_none_as_null = $treat_none_as_null,
fields = [$($tail)*],
changeset_ty = ($($changeset_ty,)*
$crate::expression::predicates::Eq<
$table_name::$column_name,
$crate::expression::bound::Bound<
<$table_name::$column_name as $crate::expression::Expression>::SqlType,
&'update $field_ty,
>,
>,
),
}
};
// Finished parsing
(
$headers:tt,
table_name = $table_name:ident,
treat_none_as_null = $treat_none_as_null:tt,
fields = [],
changeset_ty = $changeset_ty:ty,
) => {
AsChangeset!($headers, changeset_ty = $changeset_ty,);
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! AsChangeset_column_expr {
// When none_as_null is false, we don't update fields which aren't present
(
$column:expr,
$field_access:expr,
none_as_null = "false",
field_kind = option,
) => {
$field_access.as_ref().map(|f| $column.eq(f))
};
// If none_as_null is true, or the field kind isn't option, assign blindly
(
$column:expr,
$field_access:expr,
$($args:tt)*
) => {
$column.eq($field_access)
};
}

View File

@ -487,6 +487,7 @@ macro_rules! print_sql {
#[macro_use] mod parse;
#[macro_use] mod query_id;
#[macro_use] mod as_changeset;
#[macro_use] mod associations;
#[macro_use] mod identifiable;
#[macro_use] mod insertable;