Repository: diesel-rs/diesel (language: rust)

Issue:
Feature : derive(Insertable) into multiple tables
I'm currently trying to use the same struct in many tables and was looking for something like this:

#[derive(Insertable)]
#[table_name="table1", "table2"]

instead of having to make separate structs for each table. Keep up the good work!


Discussion / hints:
Ironically this used to work, but was removed when `insertable_into` was changed to `derive(Insertable_`. This has come up enough times that it's worth pursuing (I don't like the idea of giving multiple arguments to `table_name`, I think this needs a more explicit API.) Happy to do the legwork of implementing if someone proposes an API that seems reasonable.

Since this is not a feature that will require a breaking change, it is on hold until after 1.0.
Thanks for the response.
The first thing I tried was actually multiple table_name statements - maybe this is more intuitive:

#[derive(Insertable)]
#[table_name="table1"]
#[table_name="table2"]
I tried that as well before finding this issue. Seems reasonable to me.
I think that API is decent, but it has a few problems. The biggest one is that not all of our derives can handle more than one table (`Identifiable` semantically can't, `AsChangeset` can't because of its definition).

I'm fine with not being able to derive `Identifiable` on a struct that has multiple table names, as that'd be pretty nonsense anyway. Not being able to derive `AsChangeset` on the same struct is unfortunate though, as these two traits often go together.

Master is currently tracking 2.0, so we can technically make this change if we want (though it'd need a proposal on discourse following the template like all other breaking changes), but the big question is whether it's worth it.

All our derives that look at `#[table_name]` will need to be updated to handle multiple tables if they can, or error if not. It's probably worth getting a list of all affected derives before making the final call on what to do with `AsChangeset`, or if we should just go with a different, insertable specific API
Any update on this? :)
I just ran into this issue, when I tried to the pattern that @Zsarl posted above (multiple `table_name` attributes).
@Boscop We are happy to receive a fully formulated feature request for this, otherwise as always: There is no update on an issue as the issue contains no update. That won't change by asking there, so please do ask such questions.
@weiznich I'm willing to take a stab on this. In light of the upcoming 2.0 release, is there a preferred way of defining multiple table names (attribute list vs multiple attributes vs something else) as long as we have the chance to make breaking changes? If not then I'd go for the attribute list `#[table_name = foo_table, bar_table]`.
@toxeus The blocking issue here is to figure out how this interacts with other derives that use the table name. How the attribute should look like is a rather minor question.
I see, multiple derive macros share the same `table_name` attribute. The trivial solution would be to add a new `table_names` (not the s) attribute that is only used by the `Insertable` derive and accepts a list of tables. If backwards-compat is desired then it would fall back to `table_name` if `table_names` is not found.
@toxeus I think I would be fine with the following approach:

We allow both: Either `#[diesel(table_name = abc, table_name = dcf)]` or `#[diesel(table_name = abc)]` and `#[diesel(table_name = cdf)` to be present on the same struct. That gets collected internally in a `Vec<_>` by the derive crate. Now `Insertable` is able to emit more than one impl, while any other derive should emit an error message that multiple table name attributes are not supported. I would accept a PR for that if it includes:

* Compile tests showing the behaviour with other derives
* A test for the insertable case
* Documentation for the insertable case. 
..is it possible to achieve this in the current version if implementing `Insertable` manually on the struct (not using the macro)? I've also been trying to achieve this, any pointers or updates to this request would be much appreciated



Fix patch:
diff --git a/diesel_derives/src/as_changeset.rs b/diesel_derives/src/as_changeset.rs
index 5d436cb58084..c6ad20ce9e0c 100644
--- a/diesel_derives/src/as_changeset.rs
+++ b/diesel_derives/src/as_changeset.rs
@@ -7,10 +7,10 @@ use model::Model;
 use util::{inner_of_option_ty, is_option_ty, wrap_in_dummy_mod};
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, false);
+    let model = Model::from_item(&item, false, false);
 
     let struct_name = &item.ident;
-    let table_name = model.table_name();
+    let table_name = &model.table_names()[0];
 
     let fields_for_update = model
         .fields()
diff --git a/diesel_derives/src/as_expression.rs b/diesel_derives/src/as_expression.rs
index b2152049d95e..b87933e1e3b9 100644
--- a/diesel_derives/src/as_expression.rs
+++ b/diesel_derives/src/as_expression.rs
@@ -5,7 +5,7 @@ use model::Model;
 use util::{ty_for_foreign_derive, wrap_in_dummy_mod};
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, true);
+    let model = Model::from_item(&item, true, false);
 
     if model.sql_types.is_empty() {
         abort_call_site!(
diff --git a/diesel_derives/src/associations.rs b/diesel_derives/src/associations.rs
index 21bb5cd39a76..873eda6fb297 100644
--- a/diesel_derives/src/associations.rs
+++ b/diesel_derives/src/associations.rs
@@ -7,7 +7,7 @@ use parsers::BelongsTo;
 use util::{camel_to_snake, wrap_in_dummy_mod};
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, false);
+    let model = Model::from_item(&item, false, false);
 
     if model.belongs_to.is_empty() {
         abort_call_site!(
@@ -27,7 +27,7 @@ fn derive_belongs_to(item: &DeriveInput, model: &Model, assoc: &BelongsTo) -> To
     let (_, ty_generics, _) = item.generics.split_for_impl();
 
     let struct_name = &item.ident;
-    let table_name = model.table_name();
+    let table_name = &model.table_names()[0];
 
     let foreign_key = &foreign_key(assoc);
 
diff --git a/diesel_derives/src/from_sql_row.rs b/diesel_derives/src/from_sql_row.rs
index 2e5fc2e08e65..abe7c0d6c9a7 100644
--- a/diesel_derives/src/from_sql_row.rs
+++ b/diesel_derives/src/from_sql_row.rs
@@ -5,7 +5,7 @@ use model::Model;
 use util::{ty_for_foreign_derive, wrap_in_dummy_mod};
 
 pub fn derive(mut item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, true);
+    let model = Model::from_item(&item, true, false);
     let struct_ty = ty_for_foreign_derive(&item, &model);
 
     {
diff --git a/diesel_derives/src/identifiable.rs b/diesel_derives/src/identifiable.rs
index 896ab2d1ec03..1ab526324e31 100644
--- a/diesel_derives/src/identifiable.rs
+++ b/diesel_derives/src/identifiable.rs
@@ -5,10 +5,10 @@ use model::Model;
 use util::wrap_in_dummy_mod;
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, false);
+    let model = Model::from_item(&item, false, false);
 
     let struct_name = &item.ident;
-    let table_name = model.table_name();
+    let table_name = &model.table_names()[0];
 
     let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
     let mut ref_generics = item.generics.clone();
diff --git a/diesel_derives/src/insertable.rs b/diesel_derives/src/insertable.rs
index c5e3f62f74c7..7566e2314029 100644
--- a/diesel_derives/src/insertable.rs
+++ b/diesel_derives/src/insertable.rs
@@ -7,10 +7,24 @@ use syn::{DeriveInput, Expr, Path, Type};
 use util::{inner_of_option_ty, is_option_ty, wrap_in_dummy_mod};
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, false);
+    let model = Model::from_item(&item, false, true);
 
+    let tokens = model
+        .table_names()
+        .iter()
+        .map(|table_name| derive_into_single_table(&item, &model, table_name));
+
+    wrap_in_dummy_mod(quote! {
+        use diesel::insertable::Insertable;
+        use diesel::internal::derives::insertable::UndecoratedInsertRecord;
+        use diesel::prelude::*;
+
+        #(#tokens)*
+    })
+}
+
+fn derive_into_single_table(item: &DeriveInput, model: &Model, table_name: &Path) -> TokenStream {
     let treat_none_as_default_value = model.treat_none_as_default_value();
-    let table_name = &model.table_name();
     let struct_name = &item.ident;
 
     let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
@@ -114,11 +128,7 @@ pub fn derive(item: DeriveInput) -> TokenStream {
         quote! {}
     };
 
-    wrap_in_dummy_mod(quote! {
-        use diesel::insertable::Insertable;
-        use diesel::internal::derives::insertable::UndecoratedInsertRecord;
-        use diesel::prelude::*;
-
+    quote! {
         #[allow(unused_qualifications)]
         #insert_owned
 
@@ -130,7 +140,7 @@ pub fn derive(item: DeriveInput) -> TokenStream {
             #where_clause
         {
         }
-    })
+    }
 }
 
 fn field_ty_embed(field: &Field, lifetime: Option<TokenStream>) -> TokenStream {
diff --git a/diesel_derives/src/lib.rs b/diesel_derives/src/lib.rs
index 91900b9bfcfd..f173808e86bf 100644
--- a/diesel_derives/src/lib.rs
+++ b/diesel_derives/src/lib.rs
@@ -279,6 +279,7 @@ pub fn derive_identifiable(input: TokenStream) -> TokenStream {
 /// type. By default it uses the `snake_case` type name with an added `s`
 /// from the current scope.
 /// It is possible to change this default by using `#[diesel(table_name = something)]`.
+/// If `table_name` attribute is given multiple times, impls for each table are generated.
 ///
 /// If a field name of your
 /// struct differs from the name of the corresponding column,
diff --git a/diesel_derives/src/model.rs b/diesel_derives/src/model.rs
index 847fb80049af..3448853ea68a 100644
--- a/diesel_derives/src/model.rs
+++ b/diesel_derives/src/model.rs
@@ -1,4 +1,5 @@
 use proc_macro2::Span;
+use std::slice::from_ref;
 use syn::punctuated::Punctuated;
 use syn::token::Comma;
 use syn::{
@@ -13,7 +14,7 @@ use util::camel_to_snake;
 
 pub struct Model {
     name: Path,
-    table_name: Option<Path>,
+    table_names: Vec<Path>,
     pub primary_key_names: Vec<Ident>,
     treat_none_as_default_value: Option<LitBool>,
     treat_none_as_null: Option<LitBool>,
@@ -30,7 +31,11 @@ pub struct Model {
 }
 
 impl Model {
-    pub fn from_item(item: &DeriveInput, allow_unit_structs: bool) -> Self {
+    pub fn from_item(
+        item: &DeriveInput,
+        allow_unit_structs: bool,
+        allow_multiple_table: bool,
+    ) -> Self {
         let DeriveInput {
             data, ident, attrs, ..
         } = item;
@@ -50,7 +55,7 @@ impl Model {
             _ => None,
         };
 
-        let mut table_name = None;
+        let mut table_names = vec![];
         let mut primary_key_names = vec![Ident::new("id", Span::call_site())];
         let mut treat_none_as_default_value = None;
         let mut treat_none_as_null = None;
@@ -67,7 +72,15 @@ impl Model {
         for attr in parse_attributes(attrs) {
             match attr.item {
                 StructAttr::SqlType(_, value) => sql_types.push(Type::Path(value)),
-                StructAttr::TableName(_, value) => table_name = Some(value),
+                StructAttr::TableName(ident, value) => {
+                    if !allow_multiple_table && !table_names.is_empty() {
+                        abort!(
+                            ident, "expected a single table name attribute";
+                            note = "remove this attribute";
+                        )
+                    }
+                    table_names.push(value)
+                }
                 StructAttr::PrimaryKey(_, keys) => {
                     primary_key_names = keys.into_iter().collect();
                 }
@@ -92,7 +105,7 @@ impl Model {
 
         Self {
             name,
-            table_name,
+            table_names,
             primary_key_names,
             treat_none_as_default_value,
             treat_none_as_null,
@@ -109,8 +122,11 @@ impl Model {
         }
     }
 
-    pub fn table_name(&self) -> &Path {
-        self.table_name.as_ref().unwrap_or(&self.name)
+    pub fn table_names(&self) -> &[Path] {
+        match self.table_names.len() {
+            0 => from_ref(&self.name),
+            _ => &self.table_names,
+        }
     }
 
     pub fn fields(&self) -> &[Field] {
@@ -125,7 +141,7 @@ impl Model {
     }
 
     pub fn has_table_name_attribute(&self) -> bool {
-        self.table_name.is_some()
+        !self.table_names.is_empty()
     }
 
     pub fn treat_none_as_default_value(&self) -> bool {
diff --git a/diesel_derives/src/queryable.rs b/diesel_derives/src/queryable.rs
index 2ca5c3e9f0b7..e934caac42a5 100644
--- a/diesel_derives/src/queryable.rs
+++ b/diesel_derives/src/queryable.rs
@@ -6,7 +6,7 @@ use model::Model;
 use util::wrap_in_dummy_mod;
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, false);
+    let model = Model::from_item(&item, false, false);
 
     let struct_name = &item.ident;
     let field_ty = &model
diff --git a/diesel_derives/src/queryable_by_name.rs b/diesel_derives/src/queryable_by_name.rs
index 5005bc177e4a..9d0edd750181 100644
--- a/diesel_derives/src/queryable_by_name.rs
+++ b/diesel_derives/src/queryable_by_name.rs
@@ -7,7 +7,7 @@ use model::Model;
 use util::wrap_in_dummy_mod;
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, false);
+    let model = Model::from_item(&item, false, false);
 
     let struct_name = &item.ident;
     let fields = &model.fields().iter().map(get_ident).collect::<Vec<_>>();
@@ -86,7 +86,7 @@ fn get_ident(field: &Field) -> Ident {
 }
 
 fn sql_type(field: &Field, model: &Model) -> Type {
-    let table_name = model.table_name();
+    let table_name = &model.table_names()[0];
 
     match field.sql_type {
         Some(AttributeSpanWrapper { item: ref st, .. }) => st.clone(),
diff --git a/diesel_derives/src/selectable.rs b/diesel_derives/src/selectable.rs
index d799d711ae82..242560b97eaa 100644
--- a/diesel_derives/src/selectable.rs
+++ b/diesel_derives/src/selectable.rs
@@ -7,7 +7,7 @@ use model::Model;
 use util::wrap_in_dummy_mod;
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, false);
+    let model = Model::from_item(&item, false, false);
 
     let (_, ty_generics, _) = item.generics.split_for_impl();
 
@@ -137,7 +137,7 @@ fn field_column_ty(field: &Field, model: &Model) -> TokenStream {
         let embed_ty = &field.ty;
         quote!(<#embed_ty as Selectable<__DB>>::SelectExpression)
     } else {
-        let table_name = model.table_name();
+        let table_name = &model.table_names()[0];
         let column_name = field.column_name();
         quote!(#table_name::#column_name)
     }
@@ -152,7 +152,7 @@ fn field_column_inst(field: &Field, model: &Model) -> TokenStream {
         let embed_ty = &field.ty;
         quote!(<#embed_ty as Selectable<__DB>>::construct_selection())
     } else {
-        let table_name = model.table_name();
+        let table_name = &model.table_names()[0];
         let column_name = field.column_name();
         quote!(#table_name::#column_name)
     }
diff --git a/diesel_derives/src/sql_type.rs b/diesel_derives/src/sql_type.rs
index d51e60fa58f9..87b25b29efae 100644
--- a/diesel_derives/src/sql_type.rs
+++ b/diesel_derives/src/sql_type.rs
@@ -6,7 +6,7 @@ use parsers::PostgresType;
 use util::wrap_in_dummy_mod;
 
 pub fn derive(item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, true);
+    let model = Model::from_item(&item, true, false);
 
     let struct_name = &item.ident;
     let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
diff --git a/diesel_derives/src/valid_grouping.rs b/diesel_derives/src/valid_grouping.rs
index 7705a7095a01..bbb43ff740a0 100644
--- a/diesel_derives/src/valid_grouping.rs
+++ b/diesel_derives/src/valid_grouping.rs
@@ -5,7 +5,7 @@ use model::Model;
 use util::{ty_for_foreign_derive, wrap_in_dummy_mod};
 
 pub fn derive(mut item: DeriveInput) -> TokenStream {
-    let model = Model::from_item(&item, true);
+    let model = Model::from_item(&item, true, false);
     let struct_ty = ty_for_foreign_derive(&item, &model);
 
     let type_params = item


Test patch:
diff --git a/diesel_compile_tests/tests/fail/derive/multiple_table_names.rs b/diesel_compile_tests/tests/fail/derive/multiple_table_names.rs
new file mode 100644
index 000000000000..7e8ee73223f8
--- /dev/null
+++ b/diesel_compile_tests/tests/fail/derive/multiple_table_names.rs
@@ -0,0 +1,80 @@
+#[macro_use]
+extern crate diesel;
+
+table! {
+    users {
+        id -> Integer,
+        group_id -> Integer,
+    }
+}
+
+table! {
+    users_ {
+        id -> Integer,
+        group_id -> Integer,
+    }
+}
+
+table! {
+    groups {
+        id -> Integer,
+    }
+}
+
+struct Group {
+    id: i32,
+}
+
+#[derive(AsChangeset)]
+#[diesel(table_name = users, table_name = users_)]
+struct User1 {
+    id: i32,
+    group_id: i32,
+}
+
+#[derive(Associations)]
+#[diesel(belongs_to(Group))]
+#[diesel(table_name = users, table_name = users_)]
+struct User2 {
+    id: i32,
+    group_id: i32,
+}
+
+#[derive(Identifiable)]
+#[diesel(table_name = users, table_name = users_)]
+struct User3 {
+    id: i32,
+    group_id: i32,
+}
+
+#[derive(Selectable)]
+#[diesel(table_name = users)]
+#[diesel(table_name = users_)]
+struct User4 {
+    id: i32,
+    group_id: i32,
+}
+
+#[derive(Queryable)]
+#[diesel(table_name = users, table_name = users_)]
+struct User5 {
+    id: i32,
+    group_id: i32,
+}
+
+#[derive(Insertable)]
+#[diesel(table_name = users)]
+#[diesel(table_name = users_)]
+struct User6 {
+    id: i32,
+    group_id: i32,
+}
+
+#[derive(Insertable)]
+#[diesel(table_name = users, table_name = users_)]
+struct User7 {
+    id: i32,
+    group_id: i32,
+}
+
+fn main() {}
diff --git a/diesel_compile_tests/tests/fail/derive/multiple_table_names.stderr b/diesel_compile_tests/tests/fail/derive/multiple_table_names.stderr
new file mode 100644
index 000000000000..0eef5f7d6906
--- /dev/null
+++ b/diesel_compile_tests/tests/fail/derive/multiple_table_names.stderr
@@ -0,0 +1,39 @@
+error: expected a single table name attribute
+  --> tests/fail/derive/multiple_table_names.rs:29:30
+   |
+29 | #[diesel(table_name = users, table_name = users_)]
+   |                              ^^^^^^^^^^
+   |
+   = note: remove this attribute
+
+error: expected a single table name attribute
+  --> tests/fail/derive/multiple_table_names.rs:37:30
+   |
+37 | #[diesel(table_name = users, table_name = users_)]
+   |                              ^^^^^^^^^^
+   |
+   = note: remove this attribute
+
+error: expected a single table name attribute
+  --> tests/fail/derive/multiple_table_names.rs:44:30
+   |
+44 | #[diesel(table_name = users, table_name = users_)]
+   |                              ^^^^^^^^^^
+   |
+   = note: remove this attribute
+
+error: expected a single table name attribute
+  --> tests/fail/derive/multiple_table_names.rs:52:10
+   |
+52 | #[diesel(table_name = users_)]
+   |          ^^^^^^^^^^
+   |
+   = note: remove this attribute
+
+error: expected a single table name attribute
+  --> tests/fail/derive/multiple_table_names.rs:59:30
+   |
+59 | #[diesel(table_name = users, table_name = users_)]
+   |                              ^^^^^^^^^^
+   |
+   = note: remove this attribute
diff --git a/diesel_derives/tests/helpers.rs b/diesel_derives/tests/helpers.rs
index 9a21d3509f51..47f1ff0ca747 100644
--- a/diesel_derives/tests/helpers.rs
+++ b/diesel_derives/tests/helpers.rs
@@ -13,6 +13,12 @@ cfg_if! {
                 hair_color VARCHAR DEFAULT 'Green')")
                 .execute(&mut conn)
                 .unwrap();
+            sql_query("CREATE TABLE users_ (\
+                id INTEGER PRIMARY KEY AUTOINCREMENT, \
+                name VARCHAR NOT NULL, \
+                hair_color VARCHAR DEFAULT 'Green')")
+                .execute(&mut conn)
+                .unwrap();
             conn
         }
     } else if #[cfg(feature = "postgres")] {
@@ -27,12 +33,19 @@ cfg_if! {
             let mut conn = PgConnection::establish(&database_url).unwrap();
             conn.begin_test_transaction().unwrap();
             sql_query("DROP TABLE IF EXISTS users CASCADE").execute(&mut conn).unwrap();
+            sql_query("DROP TABLE IF EXISTS users_ CASCADE").execute(&mut conn).unwrap();
             sql_query("CREATE TABLE users (\
                 id SERIAL PRIMARY KEY, \
                 name VARCHAR NOT NULL, \
                 hair_color VARCHAR DEFAULT 'Green')")
                 .execute(&mut conn)
                 .unwrap();
+            sql_query("CREATE TABLE users_ (\
+                id SERIAL PRIMARY KEY, \
+                name VARCHAR NOT NULL, \
+                hair_color VARCHAR DEFAULT 'Green')")
+                .execute(&mut conn)
+                .unwrap();
             conn
         }
     } else if #[cfg(feature = "mysql")] {
@@ -46,12 +59,19 @@ cfg_if! {
                 .expect("DATABASE_URL must be set in order to run tests");
             let mut conn = MysqlConnection::establish(&database_url).unwrap();
             sql_query("DROP TABLE IF EXISTS users CASCADE").execute(&mut conn).unwrap();
+            sql_query("DROP TABLE IF EXISTS users_ CASCADE").execute(&mut conn).unwrap();
             sql_query("CREATE TABLE users (\
                 id INTEGER PRIMARY KEY AUTO_INCREMENT, \
                 name TEXT NOT NULL, \
                 hair_color VARCHAR(255) DEFAULT 'Green')")
                 .execute(&mut conn)
                 .unwrap();
+            sql_query("CREATE TABLE users_ (\
+                id INTEGER PRIMARY KEY AUTO_INCREMENT, \
+                name TEXT NOT NULL, \
+                hair_color VARCHAR(255) DEFAULT 'Green')")
+                .execute(&mut conn)
+                .unwrap();
             conn.begin_test_transaction().unwrap();
             conn
         }
diff --git a/diesel_derives/tests/insertable.rs b/diesel_derives/tests/insertable.rs
index 5f6a98f39823..dbc0d47fd208 100644
--- a/diesel_derives/tests/insertable.rs
+++ b/diesel_derives/tests/insertable.rs
@@ -105,6 +105,43 @@ fn simple_reference_definition() {
     assert_eq!(Ok(expected), saved);
 }
 
+#[test]
+fn multiple_tables() {
+    #[derive(Clone, Insertable)]
+    #[diesel(table_name = users)]
+    #[diesel(table_name = users_)]
+    struct NewUser {
+        name: String,
+        hair_color: String,
+    }
+
+    let conn = &mut connection();
+    let new_user = NewUser {
+        name: "Sean".into(),
+        hair_color: "Black".into(),
+    };
+    insert_into(users::table)
+        .values(new_user.clone())
+        .execute(conn)
+        .unwrap();
+
+    let saved = users::table
+        .select((users::name, users::hair_color))
+        .load::<(String, Option<String>)>(conn);
+    let expected = vec![("Sean".to_string(), Some("Black".to_string()))];
+    assert_eq!(Ok(expected.clone()), saved);
+
+    insert_into(users_::table)
+        .values(new_user)
+        .execute(conn)
+        .unwrap();
+
+    let saved = users_::table
+        .select((users_::name, users_::hair_color))
+        .load::<(String, Option<String>)>(conn);
+    assert_eq!(Ok(expected), saved);
+}
+
 macro_rules! test_struct_definition {
     ($test_name:ident, $struct_def:item) => {
         #[test]
diff --git a/diesel_derives/tests/schema.rs b/diesel_derives/tests/schema.rs
index aa0b9651999d..b0fabd7a956f 100644
--- a/diesel_derives/tests/schema.rs
+++ b/diesel_derives/tests/schema.rs
@@ -5,3 +5,11 @@ table! {
         hair_color -> Nullable<Text>,
     }
 }
+
+table! {
+    users_ {
+        id -> Integer,
+        name -> Text,
+        hair_color -> Nullable<Text>,
+    }
+}