1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use proc_macro2::TokenStream;
use quote::quote;
use serde_derive_internals::{attr::get_serde_meta_items, Ctxt};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Ident, Lit, Meta, NestedMeta};
fn serde_skipped(cx: &Ctxt, attrs: &[syn::Attribute]) -> bool {
for meta_items in attrs
.iter()
.filter_map(|attr| get_serde_meta_items(cx, attr).ok())
{
for meta_item in meta_items {
match meta_item {
NestedMeta::Meta(Meta::Path(path))
if path
.get_ident()
.map_or(false, |i| i.to_string() == "skip_serializing") =>
{
return true
}
_ => continue,
}
}
}
false
}
fn serde_rename(cx: &Ctxt, field: &syn::Field) -> Option<String> {
for meta_items in field
.attrs
.iter()
.filter_map(|attr| get_serde_meta_items(cx, attr).ok())
{
for meta_item in meta_items {
match meta_item {
NestedMeta::Meta(Meta::NameValue(nv))
if nv
.path
.get_ident()
.map_or(false, |i| i.to_string() == "rename") =>
{
if let Lit::Str(lit) = nv.lit {
return Some(lit.value());
}
}
_ => continue,
}
}
}
None
}
fn unraw(ident: &Ident) -> String {
ident.to_string().trim_start_matches("r#").to_owned()
}
fn column_names(data: &DataStruct) -> TokenStream {
match &data.fields {
Fields::Named(fields) => {
let cx = Ctxt::new();
let column_names_iter = fields
.named
.iter()
.filter(|f| !serde_skipped(&cx, &f.attrs))
.map(|f| match serde_rename(&cx, f) {
Some(name) => name,
None => unraw(f.ident.as_ref().unwrap()),
});
let tokens = quote! {
&[#( #column_names_iter,)*]
};
let _ = cx.check();
tokens
}
Fields::Unnamed(_) => {
quote! { &[] }
}
Fields::Unit => panic!("`Row` cannot be derived for unit structs"),
}
}
#[proc_macro_derive(Row)]
pub fn row(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let column_names = match &input.data {
Data::Struct(data) => column_names(data),
Data::Enum(_) | Data::Union(_) => panic!("`Row` can be derived only for structs"),
};
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics clickhouse::Row for #name #ty_generics #where_clause {
const COLUMN_NAMES: &'static [&'static str] = #column_names;
}
};
proc_macro::TokenStream::from(expanded)
}