Skip to content

Commit ba309e1

Browse files
authored
switch to nanosecond precision (#2016)
1 parent cbf2bdc commit ba309e1

File tree

14 files changed

+115
-98
lines changed

14 files changed

+115
-98
lines changed

columnar/src/column_values/monotonic_mapping.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@ impl MonotonicallyMappableToU64 for i64 {
139139
impl MonotonicallyMappableToU64 for DateTime {
140140
#[inline(always)]
141141
fn to_u64(self) -> u64 {
142-
common::i64_to_u64(self.into_timestamp_micros())
142+
common::i64_to_u64(self.into_timestamp_nanos())
143143
}
144144

145145
#[inline(always)]
146146
fn from_u64(val: u64) -> Self {
147-
DateTime::from_timestamp_micros(common::u64_to_i64(val))
147+
DateTime::from_timestamp_nanos(common::u64_to_i64(val))
148148
}
149149
}
150150

columnar/src/column_values/u64_based/stats_collector.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub struct StatsCollector {
2727
// This is the same as computing the difference between the values and the first value.
2828
//
2929
// This way, we can compress i64-converted-to-u64 (e.g. timestamp that were supplied in
30-
// seconds, only to be converted in microseconds).
30+
// seconds, only to be converted in nanoseconds).
3131
increment_gcd_opt: Option<(NonZeroU64, DividerU64)>,
3232
first_value_opt: Option<u64>,
3333
}

columnar/src/columnar/writer/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ impl ColumnarWriter {
266266
let mut column: ColumnWriter = column_opt.unwrap_or_default();
267267
column.record(
268268
doc,
269-
NumericalValue::I64(datetime.into_timestamp_micros()),
269+
NumericalValue::I64(datetime.into_timestamp_nanos()),
270270
arena,
271271
);
272272
column

columnar/src/value.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ impl Coerce for f64 {
109109
impl Coerce for DateTime {
110110
fn coerce(value: NumericalValue) -> Self {
111111
let timestamp_micros = i64::coerce(value);
112-
DateTime::from_timestamp_micros(timestamp_micros)
112+
DateTime::from_timestamp_nanos(timestamp_micros)
113113
}
114114
}
115115

common/src/datetime.rs

+34-20
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ pub enum DatePrecision {
1717
Milliseconds,
1818
/// Micro-seconds precision.
1919
Microseconds,
20+
/// Nano-seconds precision.
21+
Nanoseconds,
2022
}
2123

22-
/// A date/time value with microsecond precision.
24+
/// A date/time value with nanoseconds precision.
2325
///
2426
/// This timestamp does not carry any explicit time zone information.
2527
/// Users are responsible for applying the provided conversion
@@ -31,49 +33,56 @@ pub enum DatePrecision {
3133
/// to prevent unintended usage.
3234
#[derive(Clone, Default, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
3335
pub struct DateTime {
34-
// Timestamp in microseconds.
35-
pub(crate) timestamp_micros: i64,
36+
// Timestamp in nanoseconds.
37+
pub(crate) timestamp_nanos: i64,
3638
}
3739

3840
impl DateTime {
3941
/// Minimum possible `DateTime` value.
4042
pub const MIN: DateTime = DateTime {
41-
timestamp_micros: i64::MIN,
43+
timestamp_nanos: i64::MIN,
4244
};
4345

4446
/// Maximum possible `DateTime` value.
4547
pub const MAX: DateTime = DateTime {
46-
timestamp_micros: i64::MAX,
48+
timestamp_nanos: i64::MAX,
4749
};
4850

4951
/// Create new from UNIX timestamp in seconds
5052
pub const fn from_timestamp_secs(seconds: i64) -> Self {
5153
Self {
52-
timestamp_micros: seconds * 1_000_000,
54+
timestamp_nanos: seconds * 1_000_000_000,
5355
}
5456
}
5557

5658
/// Create new from UNIX timestamp in milliseconds
5759
pub const fn from_timestamp_millis(milliseconds: i64) -> Self {
5860
Self {
59-
timestamp_micros: milliseconds * 1_000,
61+
timestamp_nanos: milliseconds * 1_000_000,
6062
}
6163
}
6264

6365
/// Create new from UNIX timestamp in microseconds.
6466
pub const fn from_timestamp_micros(microseconds: i64) -> Self {
6567
Self {
66-
timestamp_micros: microseconds,
68+
timestamp_nanos: microseconds * 1_000,
69+
}
70+
}
71+
72+
/// Create new from UNIX timestamp in nanoseconds.
73+
pub const fn from_timestamp_nanos(nanoseconds: i64) -> Self {
74+
Self {
75+
timestamp_nanos: nanoseconds,
6776
}
6877
}
6978

7079
/// Create new from `OffsetDateTime`
7180
///
7281
/// The given date/time is converted to UTC and the actual
7382
/// time zone is discarded.
74-
pub const fn from_utc(dt: OffsetDateTime) -> Self {
75-
let timestamp_micros = dt.unix_timestamp() * 1_000_000 + dt.microsecond() as i64;
76-
Self { timestamp_micros }
83+
pub fn from_utc(dt: OffsetDateTime) -> Self {
84+
let timestamp_nanos = dt.unix_timestamp_nanos() as i64;
85+
Self { timestamp_nanos }
7786
}
7887

7988
/// Create new from `PrimitiveDateTime`
@@ -87,23 +96,27 @@ impl DateTime {
8796

8897
/// Convert to UNIX timestamp in seconds.
8998
pub const fn into_timestamp_secs(self) -> i64 {
90-
self.timestamp_micros / 1_000_000
99+
self.timestamp_nanos / 1_000_000_000
91100
}
92101

93102
/// Convert to UNIX timestamp in milliseconds.
94103
pub const fn into_timestamp_millis(self) -> i64 {
95-
self.timestamp_micros / 1_000
104+
self.timestamp_nanos / 1_000_000
96105
}
97106

98107
/// Convert to UNIX timestamp in microseconds.
99108
pub const fn into_timestamp_micros(self) -> i64 {
100-
self.timestamp_micros
109+
self.timestamp_nanos / 1_000
110+
}
111+
112+
/// Convert to UNIX timestamp in nanoseconds.
113+
pub const fn into_timestamp_nanos(self) -> i64 {
114+
self.timestamp_nanos
101115
}
102116

103117
/// Convert to UTC `OffsetDateTime`
104118
pub fn into_utc(self) -> OffsetDateTime {
105-
let timestamp_nanos = self.timestamp_micros as i128 * 1000;
106-
let utc_datetime = OffsetDateTime::from_unix_timestamp_nanos(timestamp_nanos)
119+
let utc_datetime = OffsetDateTime::from_unix_timestamp_nanos(self.timestamp_nanos as i128)
107120
.expect("valid UNIX timestamp");
108121
debug_assert_eq!(UtcOffset::UTC, utc_datetime.offset());
109122
utc_datetime
@@ -128,12 +141,13 @@ impl DateTime {
128141
/// Truncates the microseconds value to the corresponding precision.
129142
pub fn truncate(self, precision: DatePrecision) -> Self {
130143
let truncated_timestamp_micros = match precision {
131-
DatePrecision::Seconds => (self.timestamp_micros / 1_000_000) * 1_000_000,
132-
DatePrecision::Milliseconds => (self.timestamp_micros / 1_000) * 1_000,
133-
DatePrecision::Microseconds => self.timestamp_micros,
144+
DatePrecision::Seconds => (self.timestamp_nanos / 1_000_000_000) * 1_000_000_000,
145+
DatePrecision::Milliseconds => (self.timestamp_nanos / 1_000_000) * 1_000_000,
146+
DatePrecision::Microseconds => (self.timestamp_nanos / 1_000) * 1_000,
147+
DatePrecision::Nanoseconds => self.timestamp_nanos,
134148
};
135149
Self {
136-
timestamp_micros: truncated_timestamp_micros,
150+
timestamp_nanos: truncated_timestamp_micros,
137151
}
138152
}
139153
}

src/aggregation/bucket/histogram/date_histogram.rs

+44-37
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub struct DateHistogramAggregationReq {
4040
date_interval: Option<String>,
4141
/// The field to aggregate on.
4242
pub field: String,
43-
/// The format to format dates.
43+
/// The format to format dates. Unsupported currently.
4444
pub format: Option<String>,
4545
/// The interval to chunk your data range. Each bucket spans a value range of
4646
/// [0..fixed_interval). Accepted values
@@ -77,7 +77,7 @@ pub struct DateHistogramAggregationReq {
7777
/// hard_bounds only limits the buckets, to force a range set both extended_bounds and
7878
/// hard_bounds to the same range.
7979
///
80-
/// Needs to be provided as timestamp in microseconds precision.
80+
/// Needs to be provided as timestamp in nanosecond precision.
8181
///
8282
/// ## Example
8383
/// ```json
@@ -88,7 +88,7 @@ pub struct DateHistogramAggregationReq {
8888
/// "interval": "1d",
8989
/// "hard_bounds": {
9090
/// "min": 0,
91-
/// "max": 1420502400000000
91+
/// "max": 1420502400000000000
9292
/// }
9393
/// }
9494
/// }
@@ -114,11 +114,11 @@ impl DateHistogramAggregationReq {
114114
self.validate()?;
115115
Ok(HistogramAggregation {
116116
field: self.field.to_string(),
117-
interval: parse_into_microseconds(self.fixed_interval.as_ref().unwrap())? as f64,
117+
interval: parse_into_nanoseconds(self.fixed_interval.as_ref().unwrap())? as f64,
118118
offset: self
119119
.offset
120120
.as_ref()
121-
.map(|offset| parse_offset_into_microseconds(offset))
121+
.map(|offset| parse_offset_into_nanosecs(offset))
122122
.transpose()?
123123
.map(|el| el as f64),
124124
min_doc_count: self.min_doc_count,
@@ -155,7 +155,7 @@ impl DateHistogramAggregationReq {
155155
));
156156
}
157157

158-
parse_into_microseconds(self.fixed_interval.as_ref().unwrap())?;
158+
parse_into_nanoseconds(self.fixed_interval.as_ref().unwrap())?;
159159

160160
Ok(())
161161
}
@@ -176,9 +176,12 @@ pub enum DateHistogramParseError {
176176
/// Offset invalid
177177
#[error("passed offset is invalid {0:?}")]
178178
InvalidOffset(String),
179+
/// Value out of bounds
180+
#[error("passed value is out of bounds: {0:?}")]
181+
OutOfBounds(String),
179182
}
180183

181-
fn parse_offset_into_microseconds(input: &str) -> Result<i64, AggregationError> {
184+
fn parse_offset_into_nanosecs(input: &str) -> Result<i64, AggregationError> {
182185
let is_sign = |byte| &[byte] == b"-" || &[byte] == b"+";
183186
if input.is_empty() {
184187
return Err(DateHistogramParseError::InvalidOffset(input.to_string()).into());
@@ -187,18 +190,18 @@ fn parse_offset_into_microseconds(input: &str) -> Result<i64, AggregationError>
187190
let has_sign = is_sign(input.as_bytes()[0]);
188191
if has_sign {
189192
let (sign, input) = input.split_at(1);
190-
let val = parse_into_microseconds(input)?;
193+
let val = parse_into_nanoseconds(input)?;
191194
if sign == "-" {
192195
Ok(-val)
193196
} else {
194197
Ok(val)
195198
}
196199
} else {
197-
parse_into_microseconds(input)
200+
parse_into_nanoseconds(input)
198201
}
199202
}
200203

201-
fn parse_into_microseconds(input: &str) -> Result<i64, AggregationError> {
204+
fn parse_into_nanoseconds(input: &str) -> Result<i64, AggregationError> {
202205
let split_boundary = input
203206
.as_bytes()
204207
.iter()
@@ -226,7 +229,11 @@ fn parse_into_microseconds(input: &str) -> Result<i64, AggregationError> {
226229
_ => return Err(DateHistogramParseError::UnitNotRecognized(unit.to_string()).into()),
227230
};
228231

229-
Ok(number * multiplier_from_unit * 1000)
232+
let val = (number * multiplier_from_unit)
233+
.checked_mul(1_000_000)
234+
.ok_or_else(|| DateHistogramParseError::OutOfBounds(input.to_string()))?;
235+
236+
Ok(val)
230237
}
231238

232239
#[cfg(test)]
@@ -241,49 +248,49 @@ mod tests {
241248
use crate::Index;
242249

243250
#[test]
244-
fn test_parse_into_microseconds() {
245-
assert_eq!(parse_into_microseconds("1m").unwrap(), 60_000_000);
246-
assert_eq!(parse_into_microseconds("2m").unwrap(), 120_000_000);
251+
fn test_parse_into_nanosecs() {
252+
assert_eq!(parse_into_nanoseconds("1m").unwrap(), 60_000_000_000);
253+
assert_eq!(parse_into_nanoseconds("2m").unwrap(), 120_000_000_000);
247254
assert_eq!(
248-
parse_into_microseconds("2y").unwrap_err(),
255+
parse_into_nanoseconds("2y").unwrap_err(),
249256
DateHistogramParseError::UnitNotRecognized("y".to_string()).into()
250257
);
251258
assert_eq!(
252-
parse_into_microseconds("2000").unwrap_err(),
259+
parse_into_nanoseconds("2000").unwrap_err(),
253260
DateHistogramParseError::UnitMissing("2000".to_string()).into()
254261
);
255262
assert_eq!(
256-
parse_into_microseconds("ms").unwrap_err(),
263+
parse_into_nanoseconds("ms").unwrap_err(),
257264
DateHistogramParseError::NumberMissing("ms".to_string()).into()
258265
);
259266
}
260267

261268
#[test]
262-
fn test_parse_offset_into_microseconds() {
263-
assert_eq!(parse_offset_into_microseconds("1m").unwrap(), 60_000_000);
264-
assert_eq!(parse_offset_into_microseconds("+1m").unwrap(), 60_000_000);
265-
assert_eq!(parse_offset_into_microseconds("-1m").unwrap(), -60_000_000);
266-
assert_eq!(parse_offset_into_microseconds("2m").unwrap(), 120_000_000);
267-
assert_eq!(parse_offset_into_microseconds("+2m").unwrap(), 120_000_000);
268-
assert_eq!(parse_offset_into_microseconds("-2m").unwrap(), -120_000_000);
269-
assert_eq!(parse_offset_into_microseconds("-2ms").unwrap(), -2_000);
269+
fn test_parse_offset_into_nanosecs() {
270+
assert_eq!(parse_offset_into_nanosecs("1m").unwrap(), 60_000_000_000);
271+
assert_eq!(parse_offset_into_nanosecs("+1m").unwrap(), 60_000_000_000);
272+
assert_eq!(parse_offset_into_nanosecs("-1m").unwrap(), -60_000_000_000);
273+
assert_eq!(parse_offset_into_nanosecs("2m").unwrap(), 120_000_000_000);
274+
assert_eq!(parse_offset_into_nanosecs("+2m").unwrap(), 120_000_000_000);
275+
assert_eq!(parse_offset_into_nanosecs("-2m").unwrap(), -120_000_000_000);
276+
assert_eq!(parse_offset_into_nanosecs("-2ms").unwrap(), -2_000_000);
270277
assert_eq!(
271-
parse_offset_into_microseconds("2y").unwrap_err(),
278+
parse_offset_into_nanosecs("2y").unwrap_err(),
272279
DateHistogramParseError::UnitNotRecognized("y".to_string()).into()
273280
);
274281
assert_eq!(
275-
parse_offset_into_microseconds("2000").unwrap_err(),
282+
parse_offset_into_nanosecs("2000").unwrap_err(),
276283
DateHistogramParseError::UnitMissing("2000".to_string()).into()
277284
);
278285
assert_eq!(
279-
parse_offset_into_microseconds("ms").unwrap_err(),
286+
parse_offset_into_nanosecs("ms").unwrap_err(),
280287
DateHistogramParseError::NumberMissing("ms".to_string()).into()
281288
);
282289
}
283290

284291
#[test]
285292
fn test_parse_into_milliseconds_do_not_accept_non_ascii() {
286-
assert!(parse_into_microseconds("1m").is_err());
293+
assert!(parse_into_nanoseconds("1m").is_err());
287294
}
288295

289296
pub fn get_test_index_from_docs(
@@ -361,7 +368,7 @@ mod tests {
361368
"buckets" : [
362369
{
363370
"key_as_string" : "2015-01-01T00:00:00Z",
364-
"key" : 1420070400000000.0,
371+
"key" : 1420070400000000000.0,
365372
"doc_count" : 4
366373
}
367374
]
@@ -397,7 +404,7 @@ mod tests {
397404
"buckets" : [
398405
{
399406
"key_as_string" : "2015-01-01T00:00:00Z",
400-
"key" : 1420070400000000.0,
407+
"key" : 1420070400000000000.0,
401408
"doc_count" : 4,
402409
"texts": {
403410
"buckets": [
@@ -444,32 +451,32 @@ mod tests {
444451
"buckets": [
445452
{
446453
"doc_count": 2,
447-
"key": 1420070400000000.0,
454+
"key": 1420070400000000000.0,
448455
"key_as_string": "2015-01-01T00:00:00Z"
449456
},
450457
{
451458
"doc_count": 1,
452-
"key": 1420156800000000.0,
459+
"key": 1420156800000000000.0,
453460
"key_as_string": "2015-01-02T00:00:00Z"
454461
},
455462
{
456463
"doc_count": 0,
457-
"key": 1420243200000000.0,
464+
"key": 1420243200000000000.0,
458465
"key_as_string": "2015-01-03T00:00:00Z"
459466
},
460467
{
461468
"doc_count": 0,
462-
"key": 1420329600000000.0,
469+
"key": 1420329600000000000.0,
463470
"key_as_string": "2015-01-04T00:00:00Z"
464471
},
465472
{
466473
"doc_count": 0,
467-
"key": 1420416000000000.0,
474+
"key": 1420416000000000000.0,
468475
"key_as_string": "2015-01-05T00:00:00Z"
469476
},
470477
{
471478
"doc_count": 1,
472-
"key": 1420502400000000.0,
479+
"key": 1420502400000000000.0,
473480
"key_as_string": "2015-01-06T00:00:00Z"
474481
}
475482
]

0 commit comments

Comments
 (0)