Skip to content

Commit 1339840

Browse files
authored
Merge pull request diesel-rs#2732 from lukas-code/table_name_path
allow paths in #[table_name]
2 parents 86517d6 + cec7950 commit 1339840

15 files changed

Lines changed: 315 additions & 60 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
162162
be compatible with the rewrite, but code calling into `diesel_migrations` requires an update.
163163
See the [migration guide](#2-0-0-upgrade-migrations) for details.
164164

165+
* The `#[table_name]` attribute for derive macros can now refer to any path and is no
166+
longer limited to identifiers from the current scope.
167+
165168
### Fixed
166169

167170
* Many types were incorrectly considered non-aggregate when they should not

diesel/src/insertable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::sqlite::Sqlite;
1515
/// database. This is automatically implemented for `&[T]` and `&Vec<T>` for
1616
/// inserting more than one record.
1717
///
18-
/// This trait can be [derived](Insertable)
18+
/// This trait can be [derived](derive@Insertable)
1919
pub trait Insertable<T> {
2020
/// The `VALUES` clause to insert these records
2121
///

diesel_compile_tests/tests/fail/as_changeset_missing_table_import.stderr

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,3 @@ error[E0433]: failed to resolve: use of undeclared crate or module `users`
1111
|
1212
11 | #[table_name = "users"]
1313
| ^^^^^^^ use of undeclared crate or module `users`
14-
|
15-
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#[macro_use]
2+
extern crate diesel;
3+
4+
table! {
5+
users {
6+
id -> Integer,
7+
}
8+
}
9+
10+
#[derive(Insertable)]
11+
#[table_name = "self::users"]
12+
struct UserOk {
13+
id: i32,
14+
}
15+
16+
#[derive(Insertable)]
17+
#[table_name(self::users)]
18+
struct UserWarn {
19+
id: i32,
20+
}
21+
22+
#[derive(Insertable)]
23+
#[table_name]
24+
struct UserError1 {
25+
id: i32,
26+
}
27+
28+
#[derive(Insertable)]
29+
#[table_name = true]
30+
struct UserError2 {
31+
id: i32,
32+
}
33+
34+
#[derive(Insertable)]
35+
#[table_name = ""]
36+
struct UserError3 {
37+
id: i32,
38+
}
39+
40+
#[derive(Insertable)]
41+
#[table_name = "not a path"]
42+
struct UserError4 {
43+
id: i32,
44+
}
45+
46+
#[derive(Insertable)]
47+
#[table_name = "does::not::exist"]
48+
struct UserError5 {
49+
id: i32,
50+
}
51+
52+
fn main() {}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
warning: The form `table_name(value)` is deprecated. Use `table_name = "value"` instead
2+
--> $DIR/insertable_bad_table_name.rs:17:3
3+
|
4+
17 | #[table_name(self::users)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: `table_name` must be in the form `table_name = "value"`
8+
--> $DIR/insertable_bad_table_name.rs:23:3
9+
|
10+
23 | #[table_name]
11+
| ^^^^^^^^^^
12+
13+
error: `table_name` must be in the form `table_name = "value"`
14+
--> $DIR/insertable_bad_table_name.rs:29:3
15+
|
16+
29 | #[table_name = true]
17+
| ^^^^^^^^^^^^^^^^^
18+
19+
error: `` is not a valid path
20+
--> $DIR/insertable_bad_table_name.rs:35:16
21+
|
22+
35 | #[table_name = ""]
23+
| ^^
24+
25+
error: `not a path` is not a valid path
26+
--> $DIR/insertable_bad_table_name.rs:41:16
27+
|
28+
41 | #[table_name = "not a path"]
29+
| ^^^^^^^^^^^^
30+
31+
error[E0433]: failed to resolve: use of undeclared crate or module `does`
32+
--> $DIR/insertable_bad_table_name.rs:47:16
33+
|
34+
47 | #[table_name = "does::not::exist"]
35+
| ^^^^^^^^^^^^^^^^^^ use of undeclared crate or module `does`

diesel_compile_tests/tests/fail/insertable_missing_table_or_column.stderr

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ error[E0433]: failed to resolve: use of undeclared crate or module `posts`
1111
|
1212
16 | #[table_name = "posts"]
1313
| ^^^^^^^ use of undeclared crate or module `posts`
14-
|
15-
= note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
1614

1715
error[E0412]: cannot find type `name` in module `users`
1816
--> $DIR/insertable_missing_table_or_column.rs:24:5

diesel_derives/src/as_changeset.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
8888

8989
fn field_changeset_ty(
9090
field: &Field,
91-
table_name: &syn::Ident,
91+
table_name: &syn::Path,
9292
treat_none_as_null: bool,
9393
lifetime: Option<proc_macro2::TokenStream>,
9494
) -> syn::Type {
@@ -104,7 +104,7 @@ fn field_changeset_ty(
104104

105105
fn field_changeset_expr(
106106
field: &Field,
107-
table_name: &syn::Ident,
107+
table_name: &syn::Path,
108108
treat_none_as_null: bool,
109109
lifetime: Option<proc_macro2::TokenStream>,
110110
) -> syn::Expr {

diesel_derives/src/insertable.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use proc_macro2::Span;
33
use syn;
44

55
use field::*;
6+
use meta::path_to_string;
67
use model::*;
78
use util::*;
89

@@ -14,7 +15,7 @@ pub fn derive(item: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Diagno
1415
.error("Cannot derive Insertable for unit structs")
1516
.help(format!(
1617
"Use `insert_into({}::table).default_values()` if you want `DEFAULT VALUES`",
17-
model.table_name()
18+
path_to_string(&model.table_name())
1819
)));
1920
}
2021

@@ -123,7 +124,7 @@ fn field_expr_embed(field: &Field, lifetime: Option<proc_macro2::TokenStream>) -
123124
parse_quote!(#lifetime self#field_access)
124125
}
125126

126-
fn field_ty_serialize_as(field: &Field, table_name: &syn::Ident, ty: &syn::Type) -> syn::Type {
127+
fn field_ty_serialize_as(field: &Field, table_name: &syn::Path, ty: &syn::Type) -> syn::Type {
127128
let inner_ty = inner_of_option_ty(&ty);
128129
let column_name = field.column_name();
129130

@@ -135,7 +136,7 @@ fn field_ty_serialize_as(field: &Field, table_name: &syn::Ident, ty: &syn::Type)
135136
)
136137
}
137138

138-
fn field_expr_serialize_as(field: &Field, table_name: &syn::Ident, ty: &syn::Type) -> syn::Expr {
139+
fn field_expr_serialize_as(field: &Field, table_name: &syn::Path, ty: &syn::Type) -> syn::Expr {
139140
let field_access = field.name.access();
140141
let column_name = field.column_name();
141142
let column: syn::Expr = parse_quote!(#table_name::#column_name);
@@ -149,7 +150,7 @@ fn field_expr_serialize_as(field: &Field, table_name: &syn::Ident, ty: &syn::Typ
149150

150151
fn field_ty(
151152
field: &Field,
152-
table_name: &syn::Ident,
153+
table_name: &syn::Path,
153154
lifetime: Option<proc_macro2::TokenStream>,
154155
) -> syn::Type {
155156
let inner_ty = inner_of_option_ty(&field.ty);
@@ -165,7 +166,7 @@ fn field_ty(
165166

166167
fn field_expr(
167168
field: &Field,
168-
table_name: &syn::Ident,
169+
table_name: &syn::Path,
169170
lifetime: Option<proc_macro2::TokenStream>,
170171
) -> syn::Expr {
171172
let field_access = field.name.access();

diesel_derives/src/lib.rs

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,9 @@ use diagnostic_shim::*;
5454
/// Implements `AsChangeset`
5555
///
5656
/// To implement `AsChangeset` this derive needs to know the corresponding table
57-
/// type. By default it uses the `snake_case` type name with an added `s`.
57+
/// type. By default it uses the `snake_case` type name with an added `s` from
58+
/// the current scope.
5859
/// It is possible to change this default by using `#[table_name = "something"]`.
59-
/// In both cases the module for that table must be in scope.
60-
/// For example, to derive this for a struct called `User`, you will
61-
/// likely need a line such as `use schema::users;`
6260
///
6361
/// If a field name of your struct differs
6462
/// from the name of the corresponding column, you can annotate the field with
@@ -77,10 +75,10 @@ use diagnostic_shim::*;
7775
/// the derive should threat `None` values as `NULL`. By default
7876
/// `Option::<T>::None` is just skipped. To insert a `NULL` using default
7977
/// behavior use `Option::<Option<T>>::Some(None)`
80-
/// * `#[table_name = "some_table"]`, specifies the table for which the
81-
/// current type is a changeset. Requires that `some_table` is in scope.
78+
/// * `#[table_name = "path::to::table"]`, specifies a path to the table for which the
79+
/// current type is a changeset. The path is relative to the current module.
8280
/// If this attribute is not used, the type name converted to
83-
/// `snake_case` with an added `s` is used as table name
81+
/// `snake_case` with an added `s` is used as table name.
8482
///
8583
/// ## Optional field attributes
8684
///
@@ -148,15 +146,15 @@ pub fn derive_as_expression(input: TokenStream) -> TokenStream {
148146
///
149147
/// # Optional container attributes
150148
///
151-
/// * `#[table_name = "some_table_name"]` specifies the table this
152-
/// type belongs to. Requires that `some_table_name` is in scope.
149+
/// * `#[table_name = "path::to::table"]` specifies a path to the table this
150+
/// type belongs to. The path is relative to the current module.
153151
/// If this attribute is not used, the type name converted to
154-
/// `snake_case` with an added `s` is used as table name
152+
/// `snake_case` with an added `s` is used as table name.
155153
///
156154
/// # Optional field attributes
157155
///
158-
/// * `#[column_name = "some_table_name"]`, overrides the column the current
159-
/// field maps to to `some_table_name`. By default the field name is used
156+
/// * `#[column_name = "some_column_name"]`, overrides the column the current
157+
/// field maps to to `some_column_name`. By default the field name is used
160158
/// as column name. Only useful for the foreign key field.
161159
///
162160
#[proc_macro_derive(Associations, attributes(belongs_to, column_name, table_name))]
@@ -187,25 +185,21 @@ pub fn derive_from_sql_row(input: TokenStream) -> TokenStream {
187185
/// If it's not, you can put `#[primary_key(your_id)]` on your struct.
188186
/// If you have a composite primary key, the syntax is `#[primary_key(id1, id2)]`.
189187
///
190-
/// By default, `#[derive(Identifiable)]` will assume that your table
191-
/// name is the plural form of your struct name.
188+
/// By default, `#[derive(Identifiable)]` will assume that your table is
189+
/// in scope and its name is the plural form of your struct name.
192190
/// Diesel uses very simple pluralization rules.
193191
/// It only adds an `s` to the end, and converts `CamelCase` to `snake_case`.
194-
/// If your table name does not follow this convention
195-
/// or the plural form isn't just an `s`,
196-
/// you can specify the table name with `#[table_name = "some_table_name"]`.
197-
/// In both cases the module for that table must be in scope.
198-
/// For example, to derive this for a struct called `User`, you will
199-
/// likely need a line such as `use schema::users;`
192+
/// If your table name does not follow this convention or is not in scope,
193+
/// you can specify a path to the table with `#[table_name = "path::to::table"]`.
200194
/// Our rules for inferring table names is considered public API.
201195
/// It will never change without a major version bump.
202196
///
203197
/// # Attributes
204198
///
205199
/// ## Optional container attributes
206200
///
207-
/// * `#[table_name = "some_table_name"]` specifies the table this
208-
/// type belongs to. Requires that `some_table_name` is in scope.
201+
/// * `#[table_name = "path::to::table"]` specifies a path to the table this
202+
/// type belongs to. The path is relative to the current module.
209203
/// If this attribute is not used, the type name converted to
210204
/// `snake_case` with an added `s` is used as table name
211205
/// * `#[primary_key(id1, id2)]` to specify the struct field that
@@ -219,11 +213,9 @@ pub fn derive_identifiable(input: TokenStream) -> TokenStream {
219213
/// Implements `Insertable`
220214
///
221215
/// To implement `Insertable` this derive needs to know the corresponding table
222-
/// type. By default it uses the `snake_case` type name with an added `s`.
216+
/// type. By default it uses the `snake_case` type name with an added `s`
217+
/// from the current scope.
223218
/// It is possible to change this default by using `#[table_name = "something"]`.
224-
/// In both cases the module for that table must be in scope.
225-
/// For example, to derive this for a struct called `User`, you will
226-
/// likely need a line such as `use schema::users;`
227219
///
228220
/// If a field name of your
229221
/// struct differs from the name of the corresponding column,
@@ -250,15 +242,15 @@ pub fn derive_identifiable(input: TokenStream) -> TokenStream {
250242
///
251243
/// ## Optional container attributes
252244
///
253-
/// * `#[table_name = "some_table_name"]`, specifies the table this type
254-
/// is insertable into. Requires that `some_table_name` is in scope.
245+
/// * `#[table_name = "path::to::table"]`, specifies a path to the table this type
246+
/// is insertable into. The path is relative to the current module.
255247
/// If this attribute is not used, the type name converted to
256248
/// `snake_case` with an added `s` is used as table name
257249
///
258250
/// ## Optional field attributes
259251
///
260-
/// * `#[column_name = "some_table_name"]`, overrides the column the current
261-
/// field maps to `some_table_name`. By default the field name is used
252+
/// * `#[column_name = "some_column_name"]`, overrides the column the current
253+
/// field maps to `some_column_name`. By default the field name is used
262254
/// as column name
263255
/// * `#[diesel(embed)]`, specifies that the current field maps not only
264256
/// to single database field, but is a struct that implements `Insertable`
@@ -560,10 +552,6 @@ pub fn derive_queryable(input: TokenStream) -> TokenStream {
560552
/// `diesel::dsl::SqlTypeOf<table_name::column_name>`), or by annotating each
561553
/// field with `#[sql_type = "SomeType"]`.
562554
///
563-
/// If you are using `#[table_name]`, the module for that table must be in
564-
/// scope. For example, to derive this for a struct called `User`, you will
565-
/// likely need a line such as `use schema::users;`
566-
///
567555
/// If the name of a field on your struct is different than the column in your
568556
/// `table!` declaration, or if you are deriving this trait on a tuple struct,
569557
/// you can annotate the field with `#[column_name = "some_column"]`. For tuple
@@ -585,9 +573,10 @@ pub fn derive_queryable(input: TokenStream) -> TokenStream {
585573
///
586574
/// ## Type attributes
587575
///
588-
/// * `#[table_name = "some_table"]`, to specify that this type contains
589-
/// columns for the specified table. If no field attributes are specified
590-
/// the derive will use the sql type of the corresponding column.
576+
/// * `#[table_name = "path::to::table"]`, to specify that this type contains
577+
/// columns for the specified table. The path is relative to the current module.
578+
/// If no field attributes are specified the derive will use the sql type of
579+
/// the corresponding column.
591580
///
592581
/// ## Field attributes
593582
///

diesel_derives/src/meta.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,31 @@ impl MetaItem {
101101
}
102102
}
103103

104+
pub fn path_value(&self) -> Result<syn::Path, Diagnostic> {
105+
let maybe_attr = self.nested().ok().and_then(|mut n| n.next());
106+
let maybe_path = maybe_attr.as_ref().and_then(|m| m.path().ok());
107+
match maybe_path {
108+
Some(path) => {
109+
self.span()
110+
.warning(format!(
111+
"The form `{0}(value)` is deprecated. Use `{0} = \"value\"` instead",
112+
path_to_string(&self.name()),
113+
))
114+
.emit();
115+
Ok(path)
116+
}
117+
None => {
118+
let lit = self.lit_str_value()?;
119+
match lit.parse() {
120+
Ok(path) => Ok(path),
121+
_ => Err(lit
122+
.span()
123+
.error(format!("`{}` is not a valid path", lit.value()))),
124+
}
125+
}
126+
}
127+
}
128+
104129
pub fn expect_path(&self) -> syn::Path {
105130
self.path().unwrap_or_else(|e| {
106131
e.emit();

0 commit comments

Comments
 (0)