Skip to content

Commit 4561d44

Browse files
committed
Add #[postgres(transparent)]
1 parent 630f179 commit 4561d44

File tree

10 files changed

+251
-46
lines changed

10 files changed

+251
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use postgres_types::{FromSql, ToSql};
2+
3+
#[derive(ToSql, Debug)]
4+
#[postgres(transparent)]
5+
struct ToSqlTransparentStruct {
6+
a: i32
7+
}
8+
9+
#[derive(FromSql, Debug)]
10+
#[postgres(transparent)]
11+
struct FromSqlTransparentStruct {
12+
a: i32
13+
}
14+
15+
#[derive(ToSql, Debug)]
16+
#[postgres(transparent)]
17+
enum ToSqlTransparentEnum {
18+
Foo
19+
}
20+
21+
#[derive(FromSql, Debug)]
22+
#[postgres(transparent)]
23+
enum FromSqlTransparentEnum {
24+
Foo
25+
}
26+
27+
#[derive(ToSql, Debug)]
28+
#[postgres(transparent)]
29+
struct ToSqlTransparentTwoFieldTupleStruct(i32, i32);
30+
31+
#[derive(FromSql, Debug)]
32+
#[postgres(transparent)]
33+
struct FromSqlTransparentTwoFieldTupleStruct(i32, i32);
34+
35+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
error: #[postgres(transparent)] may only be applied to single field tuple structs
2+
--> src/compile-fail/invalid-transparent.rs:4:1
3+
|
4+
4 | / #[postgres(transparent)]
5+
5 | | struct ToSqlTransparentStruct {
6+
6 | | a: i32
7+
7 | | }
8+
| |_^
9+
10+
error: #[postgres(transparent)] may only be applied to single field tuple structs
11+
--> src/compile-fail/invalid-transparent.rs:10:1
12+
|
13+
10 | / #[postgres(transparent)]
14+
11 | | struct FromSqlTransparentStruct {
15+
12 | | a: i32
16+
13 | | }
17+
| |_^
18+
19+
error: #[postgres(transparent)] may only be applied to single field tuple structs
20+
--> src/compile-fail/invalid-transparent.rs:16:1
21+
|
22+
16 | / #[postgres(transparent)]
23+
17 | | enum ToSqlTransparentEnum {
24+
18 | | Foo
25+
19 | | }
26+
| |_^
27+
28+
error: #[postgres(transparent)] may only be applied to single field tuple structs
29+
--> src/compile-fail/invalid-transparent.rs:22:1
30+
|
31+
22 | / #[postgres(transparent)]
32+
23 | | enum FromSqlTransparentEnum {
33+
24 | | Foo
34+
25 | | }
35+
| |_^
36+
37+
error: #[postgres(transparent)] may only be applied to single field tuple structs
38+
--> src/compile-fail/invalid-transparent.rs:28:1
39+
|
40+
28 | / #[postgres(transparent)]
41+
29 | | struct ToSqlTransparentTwoFieldTupleStruct(i32, i32);
42+
| |_____________________________________________________^
43+
44+
error: #[postgres(transparent)] may only be applied to single field tuple structs
45+
--> src/compile-fail/invalid-transparent.rs:32:1
46+
|
47+
32 | / #[postgres(transparent)]
48+
33 | | struct FromSqlTransparentTwoFieldTupleStruct(i32, i32);
49+
| |_______________________________________________________^

postgres-derive-test/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::fmt;
77
mod composites;
88
mod domains;
99
mod enums;
10+
mod transparent;
1011

1112
pub fn test_type<T, S>(conn: &mut Client, sql_type: &str, checks: &[(T, S)])
1213
where
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use postgres::{Client, NoTls};
2+
use postgres_types::{FromSql, ToSql};
3+
4+
#[test]
5+
fn round_trip() {
6+
#[derive(FromSql, ToSql, Debug, PartialEq)]
7+
#[postgres(transparent)]
8+
struct UserId(i32);
9+
10+
assert_eq!(
11+
Client::connect("user=postgres host=localhost port=5433", NoTls)
12+
.unwrap()
13+
.query_one("SELECT $1::integer", &[&UserId(123)])
14+
.unwrap()
15+
.get::<_, UserId>(0),
16+
UserId(123)
17+
);
18+
}

postgres-derive/src/accepts.rs

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ use syn::Ident;
66
use crate::composites::Field;
77
use crate::enums::Variant;
88

9+
pub fn transparent_body(field: &syn::Field) -> TokenStream {
10+
let ty = &field.ty;
11+
12+
quote! {
13+
<#ty as ::postgres_types::ToSql>::accepts(type_)
14+
}
15+
}
16+
917
pub fn domain_body(name: &str, field: &syn::Field) -> TokenStream {
1018
let ty = &field.ty;
1119

postgres-derive/src/fromsql.rs

+36-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,36 @@ use crate::overrides::Overrides;
1111
pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
1212
let overrides = Overrides::extract(&input.attrs)?;
1313

14+
if overrides.name.is_some() && overrides.transparent {
15+
return Err(Error::new_spanned(
16+
&input,
17+
"#[postgres(transparent)] is not allowed with #[postgres(name = \"...\")]",
18+
));
19+
}
20+
1421
let name = overrides.name.unwrap_or_else(|| input.ident.to_string());
1522

16-
let (accepts_body, to_sql_body) = match input.data {
23+
let (accepts_body, to_sql_body) = if overrides.transparent {
24+
match input.data {
25+
Data::Struct(DataStruct {
26+
fields: Fields::Unnamed(ref fields),
27+
..
28+
}) if fields.unnamed.len() == 1 => {
29+
let field = fields.unnamed.first().unwrap();
30+
(
31+
accepts::transparent_body(field),
32+
transparent_body(&input.ident, field),
33+
)
34+
}
35+
_ => {
36+
return Err(Error::new_spanned(
37+
input,
38+
"#[postgres(transparent)] may only be applied to single field tuple structs",
39+
))
40+
}
41+
}
42+
} else {
43+
match input.data {
1744
Data::Enum(ref data) => {
1845
let variants = data
1946
.variants
@@ -55,6 +82,7 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
5582
"#[derive(FromSql)] may only be applied to structs, single field tuple structs, and enums",
5683
))
5784
}
85+
}
5886
};
5987

6088
let ident = &input.ident;
@@ -77,6 +105,13 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
77105
Ok(out)
78106
}
79107

108+
fn transparent_body(ident: &Ident, field: &syn::Field) -> TokenStream {
109+
let ty = &field.ty;
110+
quote! {
111+
<#ty as postgres_types::FromSql>::from_sql(_type, buf).map(#ident)
112+
}
113+
}
114+
80115
fn enum_body(ident: &Ident, variants: &[Variant]) -> TokenStream {
81116
let variant_names = variants.iter().map(|v| &v.name);
82117
let idents = iter::repeat(ident);

postgres-derive/src/lib.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
extern crate proc_macro;
55

66
use proc_macro::TokenStream;
7+
use syn::parse_macro_input;
78

89
mod accepts;
910
mod composites;
@@ -14,15 +15,17 @@ mod tosql;
1415

1516
#[proc_macro_derive(ToSql, attributes(postgres))]
1617
pub fn derive_tosql(input: TokenStream) -> TokenStream {
17-
let input = syn::parse(input).unwrap();
18+
let input = parse_macro_input!(input);
19+
1820
tosql::expand_derive_tosql(input)
1921
.unwrap_or_else(|e| e.to_compile_error())
2022
.into()
2123
}
2224

2325
#[proc_macro_derive(FromSql, attributes(postgres))]
2426
pub fn derive_fromsql(input: TokenStream) -> TokenStream {
25-
let input = syn::parse(input).unwrap();
27+
let input = parse_macro_input!(input);
28+
2629
fromsql::expand_derive_fromsql(input)
2730
.unwrap_or_else(|e| e.to_compile_error())
2831
.into()

postgres-derive/src/overrides.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ use syn::{Attribute, Error, Lit, Meta, NestedMeta};
22

33
pub struct Overrides {
44
pub name: Option<String>,
5+
pub transparent: bool,
56
}
67

78
impl Overrides {
89
pub fn extract(attrs: &[Attribute]) -> Result<Overrides, Error> {
9-
let mut overrides = Overrides { name: None };
10+
let mut overrides = Overrides {
11+
name: None,
12+
transparent: false,
13+
};
1014

1115
for attr in attrs {
12-
let attr = match attr.parse_meta() {
13-
Ok(meta) => meta,
14-
Err(_) => continue,
15-
};
16+
let attr = attr.parse_meta()?;
1617

1718
if !attr.path().is_ident("postgres") {
1819
continue;
@@ -39,7 +40,14 @@ impl Overrides {
3940

4041
overrides.name = Some(value);
4142
}
42-
bad => return Err(Error::new_spanned(bad, "expected a name-value meta item")),
43+
NestedMeta::Meta(Meta::Path(ref path)) => {
44+
if !path.is_ident("transparent") {
45+
return Err(Error::new_spanned(path, "unknown override"));
46+
}
47+
48+
overrides.transparent = true;
49+
}
50+
bad => return Err(Error::new_spanned(bad, "unknown attribute")),
4351
}
4452
}
4553
}

postgres-derive/src/tosql.rs

+70-37
Original file line numberDiff line numberDiff line change
@@ -11,46 +11,73 @@ use crate::overrides::Overrides;
1111
pub fn expand_derive_tosql(input: DeriveInput) -> Result<TokenStream, Error> {
1212
let overrides = Overrides::extract(&input.attrs)?;
1313

14+
if overrides.name.is_some() && overrides.transparent {
15+
return Err(Error::new_spanned(
16+
&input,
17+
"#[postgres(transparent)] is not allowed with #[postgres(name = \"...\")]",
18+
));
19+
}
20+
1421
let name = overrides.name.unwrap_or_else(|| input.ident.to_string());
1522

16-
let (accepts_body, to_sql_body) = match input.data {
17-
Data::Enum(ref data) => {
18-
let variants = data
19-
.variants
20-
.iter()
21-
.map(Variant::parse)
22-
.collect::<Result<Vec<_>, _>>()?;
23-
(
24-
accepts::enum_body(&name, &variants),
25-
enum_body(&input.ident, &variants),
26-
)
27-
}
28-
Data::Struct(DataStruct {
29-
fields: Fields::Unnamed(ref fields),
30-
..
31-
}) if fields.unnamed.len() == 1 => {
32-
let field = fields.unnamed.first().unwrap();
33-
(accepts::domain_body(&name, field), domain_body())
34-
}
35-
Data::Struct(DataStruct {
36-
fields: Fields::Named(ref fields),
37-
..
38-
}) => {
39-
let fields = fields
40-
.named
41-
.iter()
42-
.map(Field::parse)
43-
.collect::<Result<Vec<_>, _>>()?;
44-
(
45-
accepts::composite_body(&name, "ToSql", &fields),
46-
composite_body(&fields),
47-
)
23+
let (accepts_body, to_sql_body) = if overrides.transparent {
24+
match input.data {
25+
Data::Struct(DataStruct {
26+
fields: Fields::Unnamed(ref fields),
27+
..
28+
}) if fields.unnamed.len() == 1 => {
29+
let field = fields.unnamed.first().unwrap();
30+
31+
(accepts::transparent_body(field), transparent_body())
32+
}
33+
_ => {
34+
return Err(Error::new_spanned(
35+
input,
36+
"#[postgres(transparent)] may only be applied to single field tuple structs",
37+
));
38+
}
4839
}
49-
_ => {
50-
return Err(Error::new_spanned(
51-
input,
52-
"#[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums",
53-
));
40+
} else {
41+
match input.data {
42+
Data::Enum(ref data) => {
43+
let variants = data
44+
.variants
45+
.iter()
46+
.map(Variant::parse)
47+
.collect::<Result<Vec<_>, _>>()?;
48+
(
49+
accepts::enum_body(&name, &variants),
50+
enum_body(&input.ident, &variants),
51+
)
52+
}
53+
Data::Struct(DataStruct {
54+
fields: Fields::Unnamed(ref fields),
55+
..
56+
}) if fields.unnamed.len() == 1 => {
57+
let field = fields.unnamed.first().unwrap();
58+
59+
(accepts::domain_body(&name, field), domain_body())
60+
}
61+
Data::Struct(DataStruct {
62+
fields: Fields::Named(ref fields),
63+
..
64+
}) => {
65+
let fields = fields
66+
.named
67+
.iter()
68+
.map(Field::parse)
69+
.collect::<Result<Vec<_>, _>>()?;
70+
(
71+
accepts::composite_body(&name, "ToSql", &fields),
72+
composite_body(&fields),
73+
)
74+
}
75+
_ => {
76+
return Err(Error::new_spanned(
77+
input,
78+
"#[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums",
79+
));
80+
}
5481
}
5582
};
5683

@@ -78,6 +105,12 @@ pub fn expand_derive_tosql(input: DeriveInput) -> Result<TokenStream, Error> {
78105
Ok(out)
79106
}
80107

108+
fn transparent_body() -> TokenStream {
109+
quote! {
110+
postgres_types::ToSql::to_sql(&self.0, _type, buf)
111+
}
112+
}
113+
81114
fn enum_body(ident: &Ident, variants: &[Variant]) -> TokenStream {
82115
let idents = iter::repeat(ident);
83116
let variant_idents = variants.iter().map(|v| &v.ident);

0 commit comments

Comments
 (0)