Skip to content

Commit 1a4e399

Browse files
author
Matthew Badger
committed
WIP: enable time fields with non-standard names in derive macro
1 parent 8ab0fe0 commit 1a4e399

File tree

2 files changed

+89
-23
lines changed

2 files changed

+89
-23
lines changed

influxdb/tests/derive_integration_tests.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod utilities;
44
#[cfg(feature = "derive")]
55
use influxdb::InfluxDbWriteable;
66

7-
use chrono::{DateTime, Utc};
7+
use chrono::{Date, DateTime, Utc};
88
use influxdb::{Query, ReadQuery, Timestamp};
99

1010
#[cfg(feature = "serde")]
@@ -23,6 +23,20 @@ struct WeatherReading {
2323
wind_strength: Option<u64>,
2424
}
2525

26+
#[derive(Debug, PartialEq)]
27+
#[cfg_attr(feature = "derive", derive(InfluxDbWriteable))]
28+
struct WeatherReadingWithNonstandardTime {
29+
#[influxdb(time)]
30+
reading_time: DateTime<Utc>,
31+
#[influxdb(ignore)]
32+
time: DateTime<Utc>,
33+
#[influxdb(ignore)]
34+
humidity: i32,
35+
pressure: i32,
36+
#[influxdb(tag)]
37+
wind_strength: Option<u64>,
38+
}
39+
2640
#[derive(Debug)]
2741
#[cfg_attr(feature = "serde", derive(Deserialize))]
2842
struct WeatherReadingWithoutIgnored {
@@ -47,6 +61,23 @@ fn test_build_query() {
4761
);
4862
}
4963

64+
#[test]
65+
fn test_build_nonstandard_query() {
66+
let weather_reading = WeatherReadingWithNonstandardTime {
67+
reading_time: Timestamp::Hours(1).into(),
68+
time: Timestamp::Hours(1).into(),
69+
humidity: 30,
70+
pressure: 100,
71+
wind_strength: Some(5),
72+
};
73+
let query = weather_reading.into_query("weather_reading");
74+
let query = query.build().unwrap();
75+
assert_eq!(
76+
query.get(),
77+
"weather_reading,wind_strength=5 pressure=100i 3600000000000"
78+
);
79+
}
80+
5081
#[cfg(feature = "derive")]
5182
/// INTEGRATION TEST
5283
///

influxdb_derive/src/writeable.rs

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,31 @@ use syn::{
1010
#[derive(Debug)]
1111
struct WriteableField {
1212
ident: Ident,
13+
is_time: bool,
1314
is_tag: bool,
1415
is_ignore: bool,
1516
}
1617

1718
mod kw {
1819
use syn::custom_keyword;
1920

21+
custom_keyword!(time);
2022
custom_keyword!(tag);
2123
custom_keyword!(ignore);
2224
}
2325

2426
enum FieldAttr {
27+
Time(kw::time),
2528
Tag(kw::tag),
2629
Ignore(kw::ignore),
2730
}
2831

2932
impl Parse for FieldAttr {
3033
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
3134
let lookahead = input.lookahead1();
32-
if lookahead.peek(kw::tag) {
35+
if lookahead.peek(kw::time) {
36+
Ok(Self::Time(input.parse()?))
37+
} else if lookahead.peek(kw::tag) {
3338
Ok(Self::Tag(input.parse()?))
3439
} else if lookahead.peek(kw::ignore) {
3540
Ok(Self::Ignore(input.parse()?))
@@ -52,6 +57,7 @@ impl TryFrom<Field> for WriteableField {
5257

5358
fn try_from(field: Field) -> syn::Result<WriteableField> {
5459
let ident = field.ident.expect("fields without ident are not supported");
60+
let mut has_time_attr = false;
5561
let mut is_tag = false;
5662
let mut is_ignore = false;
5763

@@ -60,6 +66,7 @@ impl TryFrom<Field> for WriteableField {
6066
Meta::List(list) if list.path.is_ident("influxdb") => {
6167
for attr in syn::parse2::<FieldAttrs>(list.tokens)?.0 {
6268
match attr {
69+
FieldAttr::Time(_) => has_time_attr = true,
6370
FieldAttr::Tag(_) => is_tag = true,
6471
FieldAttr::Ignore(_) => is_ignore = true,
6572
}
@@ -69,8 +76,23 @@ impl TryFrom<Field> for WriteableField {
6976
}
7077
}
7178

79+
if [has_time_attr, is_tag, is_ignore]
80+
.iter()
81+
.filter(|&&b| b)
82+
.count()
83+
> 1
84+
{
85+
panic!("only one of time, tag, or ignore can be used");
86+
}
87+
88+
// A field is considered a time field if:
89+
// 1. It has the #[influxdb(time)] attribute, OR
90+
// 2. It's named "time" and doesn't have #[influxdb(ignore)]
91+
let is_time = has_time_attr || (ident == "time" && !is_ignore);
92+
7293
Ok(WriteableField {
7394
ident,
95+
is_time,
7496
is_tag,
7597
is_ignore,
7698
})
@@ -97,39 +119,52 @@ pub fn expand_writeable(input: DeriveInput) -> syn::Result<TokenStream> {
97119
}
98120
};
99121

100-
let time_field = format_ident!("time");
101-
let time_field_str = time_field.to_string();
102-
#[allow(clippy::cmp_owned)] // that's not how idents work clippy
103-
let fields = match fields {
122+
let writeable_fields: Vec<WriteableField> = match fields {
104123
Fields::Named(fields) => fields
105124
.named
106125
.into_iter()
107-
.filter_map(|f| {
108-
WriteableField::try_from(f)
109-
.map(|wf| {
110-
if !wf.is_ignore && wf.ident.to_string() != time_field_str {
111-
let ident = wf.ident;
112-
Some(match wf.is_tag {
113-
true => quote!(query.add_tag(stringify!(#ident), self.#ident)),
114-
false => quote!(query.add_field(stringify!(#ident), self.#ident)),
115-
})
116-
} else {
117-
None
118-
}
119-
})
120-
.transpose()
121-
})
126+
.map(WriteableField::try_from)
122127
.collect::<syn::Result<Vec<_>>>()?,
123-
_ => panic!("a struct without named fields is not supported"),
128+
_ => panic!("A struct without named fields is not supported!"),
124129
};
125130

131+
// Find the time field
132+
let mut time_field = None;
133+
for wf in &writeable_fields {
134+
if wf.is_time {
135+
if time_field.is_some() {
136+
panic!("multiple time fields found!");
137+
}
138+
time_field = Some(wf.ident.clone());
139+
}
140+
}
141+
142+
// There must be exactly one time field
143+
let time_field = time_field.expect("no time field found");
144+
145+
// Generate field assignments (excluding time and ignored fields)
146+
let field_assignments = writeable_fields
147+
.into_iter()
148+
.filter_map(|wf| {
149+
if wf.is_ignore || wf.is_time {
150+
None
151+
} else {
152+
let ident = wf.ident;
153+
Some(match wf.is_tag {
154+
true => quote!(query.add_tag(stringify!(#ident), self.#ident)),
155+
false => quote!(query.add_field(stringify!(#ident), self.#ident)),
156+
})
157+
}
158+
})
159+
.collect::<Vec<_>>();
160+
126161
Ok(quote! {
127162
impl #impl_generics ::influxdb::InfluxDbWriteable for #ident #ty_generics #where_clause {
128163
fn into_query<I: Into<String>>(self, name: I) -> ::influxdb::WriteQuery {
129164
let timestamp: ::influxdb::Timestamp = self.#time_field.into();
130165
let mut query = timestamp.into_query(name);
131166
#(
132-
query = #fields;
167+
query = #field_assignments;
133168
)*
134169
query
135170
}

0 commit comments

Comments
 (0)