Skip to content

Commit 47d1196

Browse files
authored
Merge pull request diesel-rs#532 from diesel-rs/sg-custom-pk-identifiable
Allow custom primary keys to be specified for `Identifiable`
2 parents 0ce23eb + 1e93aed commit 47d1196

10 files changed

Lines changed: 231 additions & 39 deletions

File tree

diesel/src/macros/identifiable.rs

Lines changed: 121 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,50 +28,68 @@
2828
/// ```
2929
#[macro_export]
3030
macro_rules! impl_Identifiable {
31-
// Extract table name from meta item
31+
// Find table name in options
3232
(
3333
$(())*
34-
#[table_name($table_name:ident)]
35-
$($rest:tt)*
34+
$(#[$option_name:ident $option_value:tt])*
35+
$(pub)* struct $($rest:tt)*
3636
) => {
37-
impl_Identifiable! {
38-
(table_name = $table_name,)
39-
$($rest)*
37+
__diesel_find_option_with_name! {
38+
(options = [$(#[$option_name $option_value])*], body = $($rest)*),
39+
option_name = table_name,
40+
args = [$(#[$option_name $option_value])*],
41+
callback = impl_Identifiable,
4042
}
4143
};
4244

43-
// Strip meta items that aren't table name
45+
// Next, find primary key name in options
4446
(
45-
$args:tt
46-
#[$ignore:meta]
47-
$($rest:tt)*
47+
(options = $options:tt, $($args:tt)*),
48+
found_option_with_name = table_name,
49+
value = ($table_name:ident),
4850
) => {
49-
impl_Identifiable!($args $($rest)*);
51+
__diesel_find_option_with_name! {
52+
(options = $options, table_name = $table_name, $($args)*),
53+
option_name = primary_key,
54+
args = $options,
55+
callback = impl_Identifiable,
56+
default = (id),
57+
}
5058
};
5159

52-
// Strip pub (if present) and struct from definition
53-
// After this step, we will go to the step at the bottom.
60+
// Options extracted, deal with the struct body (bottom two branches)
5461
(
55-
$args:tt
56-
$(pub)* struct $($body:tt)*
62+
(
63+
options = $ignore:tt,
64+
table_name = $table_name:ident,
65+
body = $($body:tt)*
66+
),
67+
found_option_with_name = primary_key,
68+
value = ($primary_key_name:ident),
5769
) => {
58-
impl_Identifiable!($args $($body)*);
70+
impl_Identifiable! {
71+
(
72+
table_name = $table_name,
73+
primary_key_name = $primary_key_name,
74+
)
75+
$($body)*
76+
}
5977
};
6078

61-
// We found the `id` field, return the final impl
79+
// We found the primary key field, return the final impl
6280
(
6381
(
6482
table_name = $table_name:ident,
6583
struct_ty = $struct_ty:ty,
6684
lifetimes = ($($lifetimes:tt),*),
6785
),
68-
fields = [{
69-
field_name: id,
86+
primary_key_field = {
87+
field_name: $field_name:ident,
7088
column_name: $column_name:ident,
7189
field_ty: $field_ty:ty,
7290
field_kind: $field_kind:ident,
7391
$($rest:tt)*
74-
} $($fields:tt)*],
92+
},
7593
) => {
7694
impl<$($lifetimes),*> $crate::associations::HasTable for $struct_ty {
7795
type Table = $table_name::table;
@@ -85,25 +103,45 @@ macro_rules! impl_Identifiable {
85103
type Id = &'ident $field_ty;
86104

87105
fn id(self) -> Self::Id {
88-
&self.id
106+
&self.$field_name
89107
}
90108
}
91109
};
92110

93-
// Search for the `id` field and continue
111+
// Search for the primary key field and continue
94112
(
95-
$args:tt,
113+
(
114+
table_name = $table_name:ident,
115+
primary_key_name = $primary_key_name:ident,
116+
$($args:tt)*
117+
),
96118
fields = [{
97119
field_name: $field_name:ident,
98-
column_name: $column_name:ident,
99-
field_ty: $field_ty:ty,
100-
field_kind: $field_kind:ident,
101120
$($rest:tt)*
102121
} $($fields:tt)*],
103122
) => {
104-
impl_Identifiable! {
105-
$args,
106-
fields = [$($fields)*],
123+
static_cond! {
124+
if $primary_key_name == $field_name {
125+
impl_Identifiable! {
126+
(
127+
table_name = $table_name,
128+
$($args)*
129+
),
130+
primary_key_field = {
131+
field_name: $field_name,
132+
$($rest)*
133+
},
134+
}
135+
} else {
136+
impl_Identifiable! {
137+
(
138+
table_name = $table_name,
139+
primary_key_name = $primary_key_name,
140+
$($args)*
141+
),
142+
fields = [$($fields)*],
143+
}
144+
}
107145
}
108146
};
109147

@@ -253,3 +291,57 @@ fn derive_identifiable_on_struct_with_lifetime() {
253291
assert_eq!(&"hi", foo1.id());
254292
assert_eq!(&"there", foo2.id());
255293
}
294+
295+
#[test]
296+
fn derive_identifiable_with_non_standard_pk() {
297+
use associations::Identifiable;
298+
299+
#[allow(missing_debug_implementations, missing_copy_implementations, dead_code)]
300+
struct Foo<'a> {
301+
id: i32,
302+
foo_id: &'a str,
303+
foo: i32,
304+
}
305+
306+
impl_Identifiable! {
307+
#[table_name(bars)]
308+
#[primary_key(foo_id)]
309+
struct Foo<'a> {
310+
id: i32,
311+
foo_id: &'a str,
312+
foo: i32,
313+
}
314+
}
315+
316+
let foo1 = Foo { id: 1, foo_id: "hi", foo: 2 };
317+
let foo2 = Foo { id: 2, foo_id: "there", foo: 3 };
318+
assert_eq!(&"hi", foo1.id());
319+
assert_eq!(&"there", foo2.id());
320+
}
321+
322+
#[test]
323+
fn derive_identifiable_with_non_standard_pk_given_before_table_name() {
324+
use associations::Identifiable;
325+
326+
#[allow(missing_debug_implementations, missing_copy_implementations, dead_code)]
327+
struct Foo<'a> {
328+
id: i32,
329+
foo_id: &'a str,
330+
foo: i32,
331+
}
332+
333+
impl_Identifiable! {
334+
#[primary_key(foo_id)]
335+
#[table_name(bars)]
336+
struct Foo<'a> {
337+
id: i32,
338+
foo_id: &'a str,
339+
foo: i32,
340+
}
341+
}
342+
343+
let foo1 = Foo { id: 1, foo_id: "hi", foo: 2 };
344+
let foo2 = Foo { id: 2, foo_id: "there", foo: 3 };
345+
assert_eq!(&"hi", foo1.id());
346+
assert_eq!(&"there", foo2.id());
347+
}

diesel/src/macros/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ macro_rules! table_body {
289289
pub const all_columns: ($($column_name,)+) = ($($column_name,)+);
290290

291291
#[allow(non_camel_case_types, missing_debug_implementations)]
292-
#[derive(Clone, Copy)]
292+
#[derive(Debug, Clone, Copy)]
293293
pub struct table;
294294

295295
impl table {

diesel/src/macros/parse.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,67 @@ macro_rules! __diesel_field_with_column_name {
332332
)*
333333
};
334334
}
335+
336+
#[doc(hidden)]
337+
#[macro_export]
338+
macro_rules! __diesel_find_option_with_name {
339+
// When no default is given, just a simple search will work
340+
(
341+
$headers:tt,
342+
option_name = $target_option_name:ident,
343+
args = [$(#[$option_name:ident $option_value:tt])*],
344+
callback = $callback:ident,
345+
) => { $(
346+
static_cond! {
347+
if $target_option_name == $option_name {
348+
$callback! {
349+
$headers,
350+
found_option_with_name = $target_option_name,
351+
value = $option_value,
352+
}
353+
}
354+
}
355+
)* };
356+
357+
// When a default is given, we need to recurse so we know when we're done
358+
(
359+
$headers:tt,
360+
option_name = $target_option_name:ident,
361+
args = [#[$option_name:ident $option_value:tt] $($rest:tt)*],
362+
callback = $callback:ident,
363+
default = $default:tt,
364+
) => {
365+
static_cond! {
366+
if $target_option_name == $option_name {
367+
$callback! {
368+
$headers,
369+
found_option_with_name = $target_option_name,
370+
value = $option_value,
371+
}
372+
} else {
373+
__diesel_find_option_with_name! {
374+
$headers,
375+
option_name = $target_option_name,
376+
args = [$($rest)*],
377+
callback = $callback,
378+
default = $default,
379+
}
380+
}
381+
}
382+
};
383+
384+
// Default was given, and we didn't find a value
385+
(
386+
$headers:tt,
387+
option_name = $target_option_name:ident,
388+
args = [],
389+
callback = $callback:ident,
390+
default = $default:tt,
391+
) => {
392+
$callback! {
393+
$headers,
394+
found_option_with_name = $target_option_name,
395+
value = $default,
396+
}
397+
};
398+
}

diesel_codegen/src/identifiable.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ pub fn derive_identifiable(item: syn::MacroInput) -> Tokens {
88
let table_name = model.table_name();
99
let struct_ty = &model.ty;
1010
let lifetimes = model.generics.lifetimes;
11+
let primary_key_name = model.primary_key_name;
1112
let fields = model.attrs;
12-
if !fields.iter().any(|f| f.field_name == Some(syn::Ident::new("id"))) {
13-
panic!("Could not find a field named `id` on `{}`", &model.name);
13+
if !fields.iter().any(|f| f.field_name.as_ref() == Some(&primary_key_name)) {
14+
panic!("Could not find a field named `{}` on `{}`", primary_key_name, &model.name);
1415
}
1516

1617
quote!(impl_Identifiable! {
1718
(
1819
table_name = #table_name,
20+
primary_key_name = #primary_key_name,
1921
struct_ty = #struct_ty,
2022
lifetimes = (#(#lifetimes),*),
2123
),

diesel_codegen/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub fn derive_queryable(input: TokenStream) -> TokenStream {
3838
expand_derive(input, queryable::derive_queryable)
3939
}
4040

41-
#[proc_macro_derive(Identifiable, attributes(table_name))]
41+
#[proc_macro_derive(Identifiable, attributes(table_name, primary_key))]
4242
pub fn derive_identifiable(input: TokenStream) -> TokenStream {
4343
expand_derive(input, identifiable::derive_identifiable)
4444
}

diesel_codegen/src/model.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use syn;
22

33
use attr::Attr;
4-
use util::{struct_ty, str_value_of_attr_with_name};
4+
use util::*;
55

66
pub struct Model {
77
pub ty: syn::Ty,
88
pub attrs: Vec<Attr>,
99
pub name: syn::Ident,
1010
pub generics: syn::Generics,
11+
pub primary_key_name: syn::Ident,
1112
table_name_from_annotation: Option<syn::Ident>,
1213
}
1314

@@ -22,6 +23,9 @@ impl Model {
2223
let ty = struct_ty(item.ident.clone(), &item.generics);
2324
let name = item.ident.clone();
2425
let generics = item.generics.clone();
26+
let primary_key_name = ident_value_of_attr_with_name(&item.attrs, "primary_key")
27+
.map(Clone::clone)
28+
.unwrap_or(syn::Ident::new("id"));
2529
let table_name_from_annotation = str_value_of_attr_with_name(
2630
&item.attrs, "table_name").map(syn::Ident::new);
2731

@@ -30,6 +34,7 @@ impl Model {
3034
attrs: attrs,
3135
name: name,
3236
generics: generics,
37+
primary_key_name: primary_key_name,
3338
table_name_from_annotation: table_name_from_annotation,
3439
})
3540
}

diesel_codegen_syntex/src/identifiable.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use syntax::ast;
22
use syntax::codemap::Span;
33
use syntax::ext::base::{Annotatable, ExtCtxt};
4-
use syntax::parse::token::str_to_ident;
54

65
use model::Model;
76
use util::lifetime_list_tokens;
@@ -17,18 +16,20 @@ pub fn expand_derive_identifiable(
1716
let table_name = model.table_name();
1817
let struct_ty = &model.ty;
1918
let lifetimes = lifetime_list_tokens(&model.generics.lifetimes, span);
19+
let primary_key_name = model.primary_key_name;
2020
let fields = model.field_tokens_for_stable_macro(cx);
21-
if model.attr_named(str_to_ident("id")).is_some() {
21+
if model.attr_named(primary_key_name).is_some() {
2222
push(Annotatable::Item(quote_item!(cx, impl_Identifiable! {
2323
(
2424
table_name = $table_name,
25+
primary_key_name = $primary_key_name,
2526
struct_ty = $struct_ty,
2627
lifetimes = ($lifetimes),
2728
),
2829
fields = [$fields],
2930
}).unwrap()));
3031
} else {
31-
cx.span_err(span, &format!("Could not find a field named `id` on `{}`", model.name));
32+
cx.span_err(span, &format!("Could not find a field named `{}` on `{}`", primary_key_name, model.name));
3233
}
3334
}
3435
}

0 commit comments

Comments
 (0)